putpaws 0.0.1
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/putpaws +3 -0
- data/lib/Putpawsfile +6 -0
- data/lib/putpaws/all.rb +10 -0
- data/lib/putpaws/application.rb +26 -0
- data/lib/putpaws/application_config.rb +48 -0
- data/lib/putpaws/cloud_watch/default_log_formatter.rb +11 -0
- data/lib/putpaws/cloud_watch/log_command.rb +98 -0
- data/lib/putpaws/cloud_watch/log_task.rb +1 -0
- data/lib/putpaws/cloud_watch/tasks/log_task.rake +52 -0
- data/lib/putpaws/dsl.rb +14 -0
- data/lib/putpaws/ecs/ecs_task.rb +1 -0
- data/lib/putpaws/ecs/task_command.rb +64 -0
- data/lib/putpaws/ecs/tasks/ecs_task.rake +29 -0
- data/lib/putpaws/env.rb +29 -0
- data/lib/putpaws/hello.rb +1 -0
- data/lib/putpaws/setup.rb +10 -0
- data/lib/putpaws/tasks/hello.rake +4 -0
- data/lib/putpaws/version.rb +3 -0
- data/lib/putpaws.rb +0 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2ac7d1e3c9a05d7cabf6ed3ddb76b68eebf5e238ee14cd2d220bfeaf88226570
|
4
|
+
data.tar.gz: cbcff987bb94042328f0b65d5f693d8aabd75231db1766456d4fe4ed803433e8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 76acfa6f6d0b7a514c31a0f63819cc6e1ea587a48a07a465a321afc6743bdc2b645a1d1f22aa2eb3c7c460e44cdf11d51abbc6bc11f5efbc222950a4231ae56e
|
7
|
+
data.tar.gz: daeea4af00b61ebd35b17daf562343b8839791d94dbb5ae356635d3f39d8d09d754ef971b325e979a24c846c1ea0efe878b0c910c54459a507306759a4448d47
|
data/bin/putpaws
ADDED
data/lib/Putpawsfile
ADDED
data/lib/putpaws/all.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Putpaws
|
2
|
+
class Application < Rake::Application
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
@rakefiles = %w{putpawsfile Putpawsfile putpawsfile.rb Putpawsfile.rb}
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
"putpaws"
|
10
|
+
end
|
11
|
+
|
12
|
+
def run(argv = ARGV)
|
13
|
+
Rake.application = self
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_rakefile_location
|
18
|
+
putpawsfile = File.expand_path(File.join(File.dirname(__FILE__), "..", "Putpawsfile"))
|
19
|
+
if (location = super).nil?
|
20
|
+
[putpawsfile, Dir.pwd]
|
21
|
+
else
|
22
|
+
location
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
class Putpaws::ApplicationConfig < Struct.new(
|
5
|
+
:name, :region,
|
6
|
+
:cluster, :service, :task_name_prefix, :ecs_region,
|
7
|
+
:log_group_prefix, :log_region,
|
8
|
+
keyword_init: true)
|
9
|
+
def self.load(path_prefix: '.putpaws')
|
10
|
+
@application_data ||= begin
|
11
|
+
path = Pathname.new(path_prefix).join("application.json").to_s
|
12
|
+
data = File.exists?(path) ?
|
13
|
+
JSON.parse(File.read(path), symbolize_names: true).to_h :
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.all
|
19
|
+
load.map{|k,v|
|
20
|
+
data = v.slice(*self.members)
|
21
|
+
new(data.merge(name: k.to_s))
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.find(name)
|
26
|
+
application_data = load
|
27
|
+
data = application_data[name.to_sym]
|
28
|
+
return nil unless data
|
29
|
+
data = data.slice(*self.members)
|
30
|
+
new(data.merge({name: name.to_s}))
|
31
|
+
end
|
32
|
+
|
33
|
+
def ecs_command_params
|
34
|
+
{
|
35
|
+
region: ecs_region || region,
|
36
|
+
cluster: cluster,
|
37
|
+
# service: service,
|
38
|
+
task_name_prefix: task_name_prefix,
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def log_command_params
|
43
|
+
{
|
44
|
+
region: log_region || region,
|
45
|
+
log_group_prefix: log_group_prefix,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Putpaws::CloudWatch::DefaultLogFormatter
|
2
|
+
attr_accessor :datetime_format
|
3
|
+
def initialize(datetime_format: nil)
|
4
|
+
@datetime_format = datetime_format || "%FT%T%:z"
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(event)
|
8
|
+
time = Time.at(0, event.timestamp, :millisecond)
|
9
|
+
"%s %s\n" % [time.strftime(datetime_format), event.message]
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'aws-sdk-cloudwatchlogs'
|
2
|
+
|
3
|
+
module Putpaws::CloudWatch
|
4
|
+
class LogCommand
|
5
|
+
SECONDS = {
|
6
|
+
's' => 1,
|
7
|
+
'm' => 60,
|
8
|
+
'h' => 60 * 60,
|
9
|
+
'd' => 60 * 60 * 24,
|
10
|
+
'w' => 60 * 60 * 24 * 7,
|
11
|
+
}
|
12
|
+
|
13
|
+
def self.config(config)
|
14
|
+
new(config.log_command_params)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse_unit_time(ut)
|
18
|
+
return nil unless ut
|
19
|
+
matched, number, unit = ut.match(/\A(\d+)([smhdw])\z/).to_a
|
20
|
+
return nil unless matched
|
21
|
+
number.to_i * SECONDS[unit]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.filter_args(since: nil, since_for: nil)
|
25
|
+
since_sec = parse_unit_time(since)
|
26
|
+
since_for_sec = parse_unit_time(since_for)
|
27
|
+
start_time = Time.now - (since_sec || (60*1))
|
28
|
+
end_time = if since_sec
|
29
|
+
since_for_sec && (start_time + since_for_sec)
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
args = {
|
34
|
+
start_time: start_time.to_f * 1000,
|
35
|
+
end_time: end_time && (end_time.to_f * 1000),
|
36
|
+
}
|
37
|
+
args.select{|k,v| v}
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :client
|
41
|
+
attr_reader :region, :log_group_prefix
|
42
|
+
attr_accessor :log_group
|
43
|
+
def initialize(region:, log_group_prefix: nil)
|
44
|
+
@client = Aws::CloudWatchLogs::Client.new({region: region})
|
45
|
+
@log_group_prefix = log_group_prefix
|
46
|
+
@log_group = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def list_log_groups
|
50
|
+
res = client.describe_log_groups(log_group_name_prefix: log_group_prefix)
|
51
|
+
res.log_groups
|
52
|
+
end
|
53
|
+
|
54
|
+
def log_events(**args)
|
55
|
+
raise "Log group Not Set" unless log_group
|
56
|
+
res = client.filter_log_events({
|
57
|
+
log_group_name: log_group,
|
58
|
+
**args
|
59
|
+
})
|
60
|
+
end
|
61
|
+
|
62
|
+
def tail_log_events(**args)
|
63
|
+
res = log_events(**args)
|
64
|
+
nt = newest_timestamp([args[:start_time], *res.events.map(&:timestamp)].compact.max)
|
65
|
+
events = filter_same_moment_events(res.events, args[:start_time])
|
66
|
+
next_args = if res.next_token
|
67
|
+
args.merge(next_token: res.next_token)
|
68
|
+
else
|
69
|
+
args.merge(
|
70
|
+
next_token: nil,
|
71
|
+
start_time: nt,
|
72
|
+
)
|
73
|
+
end
|
74
|
+
[events, next_args]
|
75
|
+
end
|
76
|
+
|
77
|
+
def newest_timestamp(newest)
|
78
|
+
newest = newest.to_i
|
79
|
+
if @newest_timestamp
|
80
|
+
@newest_timestamp = [@newest_timestamp, newest].max
|
81
|
+
else
|
82
|
+
@newest_timestamp = newest
|
83
|
+
end
|
84
|
+
@newest_timestamp
|
85
|
+
end
|
86
|
+
|
87
|
+
def filter_same_moment_events(events, timestamp)
|
88
|
+
@event_ids_already_shown ||= []
|
89
|
+
filtered_events = events.reject{|e| @event_ids_already_shown.include?(e.event_id)}
|
90
|
+
event_ids = events.map(&:event_id).uniq
|
91
|
+
unless event_ids.empty?
|
92
|
+
@event_ids_already_shown = event_ids
|
93
|
+
end
|
94
|
+
# pp @event_ids_already_shown
|
95
|
+
filtered_events
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
load File.expand_path("../tasks/log_task.rake", __FILE__)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "tty-prompt"
|
2
|
+
require "putpaws/cloud_watch/log_command"
|
3
|
+
require "putpaws/cloud_watch/default_log_formatter"
|
4
|
+
|
5
|
+
namespace :log do
|
6
|
+
desc "Set Log Group."
|
7
|
+
task :set_log_group do
|
8
|
+
aws = Putpaws::CloudWatch::LogCommand.config(fetch(:app))
|
9
|
+
log_groups = aws.list_log_groups.map{|a|
|
10
|
+
[a.log_group_name, a]
|
11
|
+
}.to_h
|
12
|
+
raise "Log group not found on your permission" if log_groups.empty?
|
13
|
+
|
14
|
+
log_group = if log_groups.length == 1
|
15
|
+
log_groups.first
|
16
|
+
else
|
17
|
+
prompt = TTY::Prompt.new
|
18
|
+
selected = prompt.select("Choose a log_group you're going to operate", log_groups.keys)
|
19
|
+
log_groups[selected]
|
20
|
+
end
|
21
|
+
|
22
|
+
set :log_group, log_group
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Tail log with follow."
|
26
|
+
task tailf: :set_log_group do
|
27
|
+
ENV['follow'] = '1'
|
28
|
+
Rake::Task['log:tail'].invoke
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check: https://github.com/aws/aws-cli/blob/v2/awscli/customizations/logs/tail.py
|
32
|
+
desc "Tail log."
|
33
|
+
task tail: :set_log_group do
|
34
|
+
log_group = fetch(:log_group)
|
35
|
+
log_formatter = fetch(:log_formatter) {Putpaws::CloudWatch::DefaultLogFormatter.new}
|
36
|
+
aws = Putpaws::CloudWatch::LogCommand.config(fetch(:app))
|
37
|
+
aws.log_group = log_group.log_group_name
|
38
|
+
|
39
|
+
log_event_args = Putpaws::CloudWatch::LogCommand.filter_args(since: ENV['since'], since_for: ENV['for'])
|
40
|
+
|
41
|
+
loop do
|
42
|
+
events, next_args = aws.tail_log_events(**log_event_args)
|
43
|
+
events.each {|a| puts log_formatter.call(a)}
|
44
|
+
log_event_args = next_args
|
45
|
+
unless log_event_args[:next_token]
|
46
|
+
ENV['follow'] ? sleep(5) : break
|
47
|
+
end
|
48
|
+
rescue Interrupt
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/putpaws/dsl.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
load File.expand_path("../tasks/ecs_task.rake", __FILE__)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'aws-sdk-ecs'
|
2
|
+
|
3
|
+
module Putpaws::Ecs
|
4
|
+
class TaskCommand
|
5
|
+
def self.config(config)
|
6
|
+
new(config.ecs_command_params)
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :ecs_client
|
10
|
+
attr_reader :region, :cluster, :task_name_prefix
|
11
|
+
attr_accessor :ecs_task
|
12
|
+
def initialize(region:, cluster:, task_name_prefix: nil)
|
13
|
+
@ecs_client = Aws::ECS::Client.new({region: region})
|
14
|
+
@region = region
|
15
|
+
@cluster = cluster
|
16
|
+
@task_name_prefix = task_name_prefix
|
17
|
+
@ecs_task = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def list_ecs_tasks
|
21
|
+
res = ecs_client.list_tasks(cluster: cluster)
|
22
|
+
res = ecs_client.describe_tasks(tasks: res.task_arns, cluster: cluster)
|
23
|
+
return res.tasks unless task_name_prefix
|
24
|
+
res.tasks.select{|t|
|
25
|
+
_, name = t.task_definition_arn.split('task-definition/')
|
26
|
+
name.start_with?(task_name_prefix)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_attach_command(container: 'app')
|
31
|
+
raise "ECS Task Not Set" unless ecs_task
|
32
|
+
ctn = ecs_task.containers.detect{|c| c.name == container}
|
33
|
+
task_id = ecs_task.task_arn.split('/').last
|
34
|
+
raise "Container: #{container} not found" unless ctn
|
35
|
+
res = ecs_client.execute_command({
|
36
|
+
cluster: cluster,
|
37
|
+
container: container,
|
38
|
+
command: '/bin/bash',
|
39
|
+
interactive: true,
|
40
|
+
task: ecs_task.task_arn,
|
41
|
+
})
|
42
|
+
|
43
|
+
# https://github.com/aws/aws-cli/blob/2a6136010d8656a605d41d1e7b5fdab3c2930cad/awscli/customizations/ecs/executecommand.py#L105
|
44
|
+
session_json = {
|
45
|
+
"SessionId" => res.session.session_id,
|
46
|
+
"StreamUrl" => res.session.stream_url,
|
47
|
+
"TokenValue" => res.session.token_value,
|
48
|
+
}.to_json
|
49
|
+
target_json = {
|
50
|
+
"Target" => "ecs:#{cluster}_#{task_id}_#{ctn.runtime_id}"
|
51
|
+
}.to_json
|
52
|
+
cmd = [
|
53
|
+
"session-manager-plugin",
|
54
|
+
session_json.dump,
|
55
|
+
@region,
|
56
|
+
"StartSession",
|
57
|
+
'test',
|
58
|
+
target_json.dump,
|
59
|
+
"https://ssm.ap-northeast-1.amazonaws.com"
|
60
|
+
]
|
61
|
+
cmd.join(' ')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "tty-prompt"
|
2
|
+
require "putpaws/ecs/task_command"
|
3
|
+
|
4
|
+
namespace :ecs do
|
5
|
+
desc "Set ECS task."
|
6
|
+
task :set_task do
|
7
|
+
aws = Putpaws::Ecs::TaskCommand.config(fetch(:app))
|
8
|
+
ecs_tasks = aws.list_ecs_tasks.map{|t|
|
9
|
+
task_id = t.task_arn.split('/').last
|
10
|
+
task_def = t.task_definition_arn.split('/').last
|
11
|
+
["#{task_id} (#{task_def}) #{t.last_status}", t]
|
12
|
+
}.to_h
|
13
|
+
prompt = TTY::Prompt.new
|
14
|
+
selected = prompt.select("Choose a task you're going to operate", ecs_tasks.keys)
|
15
|
+
ecs_task = ecs_tasks[selected]
|
16
|
+
|
17
|
+
set :ecs_task, ecs_task
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Attach on ECS task. You need to enable ECS Exec on a specified task."
|
21
|
+
task attach: :set_task do
|
22
|
+
ecs_task = fetch(:ecs_task)
|
23
|
+
aws = Putpaws::Ecs::TaskCommand.config(fetch(:app))
|
24
|
+
aws.ecs_task = ecs_task
|
25
|
+
cmd = aws.get_attach_command
|
26
|
+
puts cmd
|
27
|
+
system(cmd)
|
28
|
+
end
|
29
|
+
end
|
data/lib/putpaws/env.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# require 'delegate'
|
2
|
+
|
3
|
+
class Putpaws::Env
|
4
|
+
def self.default
|
5
|
+
@env ||= new({})
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :values
|
9
|
+
|
10
|
+
def initialize(values)
|
11
|
+
@values = values
|
12
|
+
end
|
13
|
+
|
14
|
+
def set(key, value=nil, &block)
|
15
|
+
values[key] = block || value
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch(key, default=nil, &block)
|
19
|
+
fetch_for(key, default, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch_for(key, default, &block)
|
23
|
+
block ? values.fetch(key, &block) : values.fetch(key, default)
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(key)
|
27
|
+
values.delete(key)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
load File.expand_path("../tasks/hello.rake", __FILE__)
|
data/lib/putpaws.rb
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: putpaws
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- metheglin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '13.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '13.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tty-prompt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: aws-sdk-ecs
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: aws-sdk-cloudwatchlogs
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
description: aws fargate based infra management
|
70
|
+
email: pigmybank@gmail.com
|
71
|
+
executables:
|
72
|
+
- putpaws
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- bin/putpaws
|
77
|
+
- lib/Putpawsfile
|
78
|
+
- lib/putpaws.rb
|
79
|
+
- lib/putpaws/all.rb
|
80
|
+
- lib/putpaws/application.rb
|
81
|
+
- lib/putpaws/application_config.rb
|
82
|
+
- lib/putpaws/cloud_watch/default_log_formatter.rb
|
83
|
+
- lib/putpaws/cloud_watch/log_command.rb
|
84
|
+
- lib/putpaws/cloud_watch/log_task.rb
|
85
|
+
- lib/putpaws/cloud_watch/tasks/log_task.rake
|
86
|
+
- lib/putpaws/dsl.rb
|
87
|
+
- lib/putpaws/ecs/ecs_task.rb
|
88
|
+
- lib/putpaws/ecs/task_command.rb
|
89
|
+
- lib/putpaws/ecs/tasks/ecs_task.rake
|
90
|
+
- lib/putpaws/env.rb
|
91
|
+
- lib/putpaws/hello.rb
|
92
|
+
- lib/putpaws/setup.rb
|
93
|
+
- lib/putpaws/tasks/hello.rake
|
94
|
+
- lib/putpaws/version.rb
|
95
|
+
homepage: https://rubygems.org/gems/putpaws
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '2.7'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubygems_version: 3.1.4
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Put your paws up!!
|
118
|
+
test_files: []
|