triton-ops 0.18.4.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 23ccae6a1a3b477925806a3cf9d86a1154990c67fff3b1b9aaec9b9b3cd9a9f2
4
+ data.tar.gz: b4a5d9451580ebecb5e410a347e53e763844f35e1a9bc60bfeedca4faf5f95c6
5
+ SHA512:
6
+ metadata.gz: 88b343686ee3114d4c8738df04fe268de7e069195d06b0ce5228dcf96577d1333293fed49d057d1879b9190b69451fe0d8e5ded0ddb1d34641755e6b272883c5
7
+ data.tar.gz: 07df2db88ef327805083f2b02001a5b1db70454b9a76e851b5f1c0360e4494685609c87b5d07ef8eca5f6c4c34e0f91ebecf44b1d9467284b85ce6463f314748
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+
2
+ The MIT License (MIT)
3
+ Copyright © 2018 Chris Olstrom <chris@olstrom.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.org ADDED
@@ -0,0 +1,40 @@
1
+ #+TITLE: Triton-Ops - A Ruby Interface for Triton Operators
2
+ #+LATEX: \pagebreak
3
+
4
+ * Overview
5
+
6
+ ~triton-ops~ provides Contract-based models for working with the parts of
7
+ Triton that aren't exposed through the Cloud API. Specifically, it consumes
8
+ JSON produced by tools on a Triton Headnode over SSH, records a snapshot of
9
+ that data, and provides a predictable set of Objects to explore those
10
+ snapshots.
11
+
12
+ * Requirements
13
+
14
+ - SSH Access to a Triton Headnode
15
+ - A local Redis to record snapshots.
16
+
17
+ * Usage
18
+
19
+ ~triton-ops~ includes two utilities to get you started. For more detailed
20
+ usage, invoke them with =--help=.
21
+
22
+ ** triton-ops-collect
23
+
24
+ This program connects to a headnode over SSH, collects data using standard
25
+ tools included with Triton, and records this information to Redis.
26
+
27
+ ** triton-ops-report
28
+
29
+ This program generates usage reports from snapshots recorded by
30
+ ~triton-ops-collect~.
31
+
32
+ * License
33
+
34
+ ~triton-ops~ is available under the [[https://tldrlegal.com/license/mit-license][MIT License]]. See ~LICENSE.txt~ for the
35
+ full text.
36
+
37
+ * Contributors
38
+
39
+ - [[https://colstrom.github.io/][Chris Olstrom]] | [[mailto:chris@olstrom.com][e-mail]] | [[https://twitter.com/ChrisOlstrom][Twitter]]
40
+ - Mark Yen
@@ -0,0 +1,76 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- ruby -*-
3
+
4
+ require 'json' # Ruby Standard Library
5
+ require 'optparse' # Ruby Standard Library
6
+ require 'pry' # MIT License
7
+ require 'tty-spinner' # MIT License
8
+
9
+ require_relative '../lib/triton-ops/snapshot/collector'
10
+ require_relative '../lib/triton-ops/snapshot/explorer'
11
+
12
+ feature = {
13
+ interactive: ENV.fetch('INTERACTIVE') { STDIN.tty? } != 'false',
14
+ namespace: ENV.fetch('NAMESPACE') { '' },
15
+ }
16
+
17
+ ####################################
18
+ # Option Handling and Program Help #
19
+ ####################################
20
+
21
+ OptionParser.new do |options|
22
+ program = File.basename $PROGRAM_NAME
23
+
24
+ # Interactive and non-interactive usage differ slightly. While the form is
25
+ # consistent, non-interactive usage *requires* arguments, which could
26
+ # otherwise be resolved with interactive prompts.
27
+ banner = {
28
+ 'interactive' => "usage: #{program} [--interactive] [options] [headnode]",
29
+ 'non-interactive' => "usage: #{program} --non-interactive [options] <headnode>",
30
+ }
31
+
32
+ # To emphasize the difference between interactive and non-interactive usage,
33
+ # the currently *active* mode is presented in bold typeface.
34
+ options.on('-h', '--help', "Show this help text") do
35
+ active = feature[:interactive] ? 'interactive' : 'non-interactive'
36
+ banner[active] = Pastel.new.bold banner[active]
37
+ options.banner = banner.values.join("\n")
38
+
39
+ puts options
40
+ exit
41
+ end
42
+
43
+ options.on('-i', '--interactive', "Enable interactive prompts (default)") { feature[:interactive] = true }
44
+ options.on('-n', '--non-interactive', "Disable interactive prompts") { feature[:interactive] = false }
45
+ options.on('-N', '--namespace NAMESPACE', "Use NAMESPACE for Redis keys") { |o| feature[:namespace] = o }
46
+ end.parse!
47
+
48
+ #####################
49
+ # Argument Handling #
50
+ #####################
51
+
52
+ begin
53
+ interactive = ::TritonOps::Snapshot::Explorer.new namespace: feature[:namespace]
54
+ headnode = ARGV.shift || (interactive.headnode if feature[:interactive])
55
+ rescue Interrupt, TTY::Reader::InputInterrupt
56
+ exit Errno::EINTR::Errno
57
+ end
58
+
59
+ exit Errno::EINVAL::Errno if [headnode].any?(&:nil?)
60
+
61
+ ################
62
+ # Main Program #
63
+ ################
64
+
65
+ collector = TritonOps::Snapshot::Collector.new headnode, namespace: feature[:namespace]
66
+
67
+ %w(VMs Users Images Servers Platforms).map do |target|
68
+ TTY::Spinner.new("[:spinner] Collecting #{target} ...", format: :arrow_pulse).run('done!') do
69
+ target.downcase!
70
+ collector.record target, collector.method(target).call
71
+ end
72
+ end
73
+
74
+ puts "#{headnode} #{collector.timestamp.iso8601}"
75
+
76
+ binding.pry if ENV.fetch('HACKING', 'false') != 'false'
@@ -0,0 +1,93 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- ruby -*-
3
+
4
+ require 'json' # Ruby Standard Library
5
+ require 'optparse' # Ruby Standard Library
6
+ require 'pry' # MIT License
7
+
8
+ require_relative '../lib/triton-ops/snapshot'
9
+ require_relative '../lib/triton-ops/snapshot/explorer'
10
+ require_relative '../lib/triton-ops/snapshot/reporter'
11
+
12
+ feature = {
13
+ exclude: [],
14
+ format: :unicode,
15
+ interactive: ENV.fetch('INTERACTIVE') { STDIN.tty? } != 'false',
16
+ namespace: ENV.fetch('NAMESPACE') { '' },
17
+ }
18
+
19
+ ####################################
20
+ # Option Handling and Program Help #
21
+ ####################################
22
+
23
+ OptionParser.new do |options|
24
+ program = File.basename $PROGRAM_NAME
25
+
26
+ # Interactive and non-interactive usage differ slightly. While the form is
27
+ # consistent, non-interactive usage *requires* arguments, which could
28
+ # otherwise be resolved with interactive prompts.
29
+ banner = {
30
+ 'interactive' => "usage: #{program} [--interactive] [options] [headnode] [snapshot] [user ...]",
31
+ 'non-interactive' => "usage: #{program} --non-interactive [options] <headnode> <snapshot> [user ...]",
32
+ }
33
+
34
+ # To emphasize the difference between interactive and non-interactive usage,
35
+ # the currently *active* mode is presented in bold typeface.
36
+ options.on('-h', '--help', "Show this help text") do
37
+ active = feature[:interactive] ? 'interactive' : 'non-interactive'
38
+ banner[active] = Pastel.new.bold banner[active]
39
+ options.banner = banner.values.join("\n")
40
+
41
+ puts options
42
+ exit
43
+ end
44
+
45
+ options.on('-i', '--interactive', "Enable interactive prompts (default)") { feature[:interactive] = true }
46
+ options.on('-n', '--non-interactive', "Disable interactive prompts") { feature[:interactive] = false }
47
+ options.on('-N', '--namespace NAMESPACE', "Use NAMESPACE for Redis keys") { |o| feature[:namespace] = o }
48
+ options.on('-E', '--exclude LOGIN', "Exclude LOGIN from report") { |o| feature[:exclude] << o }
49
+ options.on('-F', '--format FORMAT', "Format output as (basic|ascii|unicode). Defaults to unicode.") do |o|
50
+ feature[:format] = case o
51
+ when /^b(asic)?$/
52
+ :basic
53
+ when /^a(scii)?$/
54
+ :ascii
55
+ when /^u(nicode)?$/
56
+ :unicode
57
+ else
58
+ STDERR.puts Pastel.new.red "#{program}: error: option (--format): #{Pastel.new.bold o} is not one of ([b]asic|[a]scii|[u]nicode)"
59
+ exit Errno::EINVAL::Errno
60
+ end
61
+ end
62
+
63
+ end.parse!
64
+
65
+ #####################
66
+ # Argument Handling #
67
+ #####################
68
+
69
+ begin
70
+ interactive = ::TritonOps::Snapshot::Explorer.new namespace: feature[:namespace]
71
+ headnode = ARGV.shift || (interactive.headnode if feature[:interactive])
72
+ timestamp = ARGV.shift || (interactive.snapshot headnode if feature[:interactive])
73
+ rescue Interrupt, TTY::Reader::InputInterrupt
74
+ exit Errno::EINTR::Errno
75
+ end
76
+
77
+ exit Errno::EINVAL::Errno if [headnode, timestamp].any?(&:nil?)
78
+
79
+ ################
80
+ # Main Program #
81
+ ################
82
+
83
+ snapshot = TritonOps::Snapshot.new headnode, timestamp
84
+
85
+ reporter = TritonOps::Snapshot::Reporter.new(
86
+ snapshot,
87
+ format: feature[:format],
88
+ exclude: feature[:exclude],
89
+ targets: ARGV)
90
+
91
+ reporter.usage
92
+
93
+ binding.pry if ENV.fetch('HACKING', 'false') != 'false'
@@ -0,0 +1,195 @@
1
+
2
+ # -*- ruby -*-
3
+
4
+ require 'contracts' # BSD-2-Clause License
5
+
6
+ require_relative '../support/feature/comparable_as_hash'
7
+ require_relative '../support/feature/hash_from_initialization_contract'
8
+ require_relative '../support/types'
9
+ require_relative '../support/type_coercion'
10
+ require_relative 'user'
11
+
12
+ module TritonOps
13
+ module Resource
14
+ class Image
15
+ include ::Contracts::Core
16
+ include ::Contracts::Builtin
17
+ include ::TritonOps::Support::Feature::ComparableAsHash
18
+ include ::TritonOps::Support::Feature::HashFromInitializationContract
19
+ include ::TritonOps::Support::Types
20
+ include ::TritonOps::Support::TypeCoercion
21
+
22
+ Type = Enum[*%w(zone-dataset docker lx-dataset zvol other)]
23
+ OS = Enum[*%w(smartos linux other bsd)]
24
+ State = Enum[*%w(active)]
25
+ NICDriver = Enum[*%w(virtio)]
26
+ DiskDriver = Enum[*%w(virtio)]
27
+ CPU = Enum[*%w(host)]
28
+
29
+ Contract ({
30
+ :v => Integer,
31
+ :uuid => String,
32
+ :owner => String,
33
+ :name => String,
34
+ :version => String,
35
+ :state => State,
36
+ :disabled => Bool,
37
+ :public => Bool,
38
+ :published_at => Or[String, Time],
39
+ :type => Type,
40
+ :os => OS,
41
+ :files => ArrayOf[HashOf[Or[String, Symbol], Any]],
42
+
43
+ :description => Maybe[String],
44
+ :requirements => Maybe[HashOf[Or[String, Symbol], Any]],
45
+ :tags => Maybe[HashOf[Or[String, Symbol], Any]],
46
+ :homepage => Maybe[String],
47
+ :origin => Maybe[String],
48
+ :urn => Maybe[String],
49
+ :users => Maybe[ArrayOf[HashOf[Or[String, Symbol], Any]]],
50
+ :nic_driver => Maybe[NICDriver],
51
+ :disk_driver => Maybe[DiskDriver],
52
+ :cpu_type => Maybe[CPU],
53
+ :image_size => Maybe[Integer],
54
+ }) => Image
55
+ def initialize(**options)
56
+ @options = options
57
+ self.to_h
58
+ remove_instance_variable '@options'
59
+ self
60
+ end
61
+
62
+ #######################
63
+ # Required Properties #
64
+ #######################
65
+
66
+ Contract None => Integer
67
+ def v
68
+ @v ||= @options.fetch :v
69
+ end
70
+
71
+ Contract None => String
72
+ def uuid
73
+ @uuid ||= @options.fetch :uuid
74
+ end
75
+
76
+ Contract None => String
77
+ def owner
78
+ @owner ||= @options.fetch :owner
79
+ end
80
+
81
+ Contract None => String
82
+ def name
83
+ @name ||= @options.fetch :owner
84
+ end
85
+
86
+ Contract None => String
87
+ def version
88
+ @version ||= @options.fetch :owner
89
+ end
90
+
91
+ Contract None => State
92
+ def state
93
+ @state ||= @options.fetch :state
94
+ end
95
+
96
+ Contract None => Bool
97
+ def disabled
98
+ @disabled ||= (@options || {}).fetch :disabled, false
99
+ end
100
+
101
+ Contract None => Bool
102
+ def public
103
+ @public ||= (@options || {}).fetch :public, false
104
+ end
105
+
106
+ Contract None => Time
107
+ def published_at
108
+ @published_at ||= Coerce.to_time @options.fetch :published_at
109
+ end
110
+
111
+ Contract None => Type
112
+ def type
113
+ @type ||= @options.fetch :type
114
+ end
115
+
116
+ Contract None => OS
117
+ def os
118
+ @os ||= @options.fetch :os
119
+ end
120
+
121
+ Contract None => ArrayOf[HashOf[Symbol, Any]]
122
+ def files
123
+ @files ||= @options.fetch(:files).map { |file| file.transform_keys(&:to_sym) }
124
+ end
125
+
126
+ alias disabled? disabled
127
+ alias public? public
128
+
129
+ ################################################
130
+ # Optional Properties (with sensible defaults) #
131
+ ################################################
132
+
133
+ Contract None => HashOf[Symbol, Any]
134
+ def requirements
135
+ @requirements ||= @options.fetch(:requirements, {}).transform_keys(&:to_sym)
136
+ end
137
+
138
+ Contract None => HashOf[String, Any]
139
+ def tags
140
+ @tags ||= @options.fetch(:tags, {}).transform_keys(&:to_s)
141
+ end
142
+
143
+ Contract None => ArrayOf[HashOf[Symbol, String]]
144
+ def users
145
+ @users ||= @options.fetch :users, []
146
+ end
147
+
148
+ ##########################################
149
+ # Optional Properties (without defaults) #
150
+ ##########################################
151
+
152
+ Contract None => Maybe[String]
153
+ def description
154
+ @description ||= (@options || {}).fetch :description, nil
155
+ end
156
+
157
+ Contract None => Maybe[String]
158
+ def homepage
159
+ @homepage ||= (@options || {}).fetch :homepage, nil
160
+ end
161
+
162
+ Contract None => Maybe[String]
163
+ def origin
164
+ @origin ||= (@options || {}).fetch :origin, nil
165
+ end
166
+
167
+ Contract None => Maybe[String]
168
+ def urn
169
+ @urn ||= (@options || {}).fetch :urn, nil
170
+ end
171
+
172
+ Contract None => Maybe[NICDriver]
173
+ def nic_driver
174
+ @nic_driver ||= (@options || {}).fetch :nic_driver, nil
175
+ end
176
+
177
+ Contract None => Maybe[DiskDriver]
178
+ def disk_driver
179
+ @disk_driver ||= (@options || {}).fetch :disk_driver, nil
180
+ end
181
+
182
+ Contract None => Maybe[CPU]
183
+ def cpu_type
184
+ @cpu_type ||= (@options || {}).fetch :cpu_type, nil
185
+ end
186
+
187
+ Contract None => Maybe[Integer]
188
+ def image_size
189
+ @image_size ||= (@options || {}).fetch :image_size, nil
190
+ end
191
+
192
+ alias size image_size
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,81 @@
1
+
2
+ # -*- ruby -*-
3
+
4
+ require 'contracts' # BSD-2-Clause License
5
+ require 'time' # Ruby Standard Library
6
+
7
+ require_relative '../support/feature/comparable_as_hash'
8
+ require_relative '../support/feature/hash_from_initialization_contract'
9
+ require_relative '../support/type_coercion'
10
+ require_relative '../support/types'
11
+
12
+ module TritonOps
13
+ module Resource
14
+ class Platform
15
+ include ::Contracts::Core
16
+ include ::Contracts::Builtin
17
+ include ::TritonOps::Support::Feature::ComparableAsHash
18
+ include ::TritonOps::Support::Feature::HashFromInitializationContract
19
+ include ::TritonOps::Support::TypeCoercion
20
+ include ::TritonOps::Support::Types
21
+
22
+ ServerReference = {
23
+ hostname: String,
24
+ uuid: String,
25
+ }
26
+
27
+ Contract ({
28
+ :boot_platform => ArrayOf[ServerReference],
29
+ :current_platform => ArrayOf[ServerReference],
30
+ :default => Bool,
31
+ :latest => Bool,
32
+ :usb_key => Bool,
33
+ :version => Castable::Time,
34
+ }) => Platform
35
+ def initialize(**options)
36
+ @options = options
37
+ self.to_h
38
+ remove_instance_variable '@options'
39
+ self
40
+ end
41
+
42
+ #######################
43
+ # Required Properties #
44
+ #######################
45
+
46
+ Contract None => Time
47
+ def version
48
+ @version ||= Coerce.to_time @options.fetch :version
49
+ end
50
+
51
+ Contract None => ArrayOf[ServerReference]
52
+ def boot_platform
53
+ @boot_platform ||= @options.fetch(:boot_platform).map { |hash| hash.transform_keys(&:to_sym) }
54
+ end
55
+
56
+ Contract None => ArrayOf[ServerReference]
57
+ def current_platform
58
+ @current_platform ||= @options.fetch(:current_platform).map { |hash| hash.transform_keys(&:to_sym) }
59
+ end
60
+
61
+ Contract None => Bool
62
+ def latest
63
+ @latest ||= (@options || {}).fetch(:latest, false)
64
+ end
65
+
66
+ Contract None => Bool
67
+ def default
68
+ @default ||= (@options || {}).fetch(:default, false)
69
+ end
70
+
71
+ Contract None => Bool
72
+ def usb_key
73
+ @usb_key ||= (@options || {}).fetch(:usb_key, false)
74
+ end
75
+
76
+ alias latest? latest
77
+ alias default? default
78
+ alias usb_key? usb_key
79
+ end
80
+ end
81
+ end