triton-ops 0.18.4.pre

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 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