instrumental_tools 1.0.0 → 1.1.3

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/BUILD.md +15 -0
  3. data/CHANGELOG.md +17 -1
  4. data/CUSTOM_METRICS.md +4 -0
  5. data/INSTALL.md +12 -7
  6. data/README.md +2 -2
  7. data/Rakefile +202 -64
  8. data/TEST.md +18 -0
  9. data/bin/instrument_server +31 -15
  10. data/chef/.kitchen.yml +10 -1
  11. data/chef/instrumental_tools/attributes/default.rb +29 -2
  12. data/chef/instrumental_tools/recipes/default.rb +188 -17
  13. data/chef/instrumental_tools/templates/default/instrument_server.erb +46 -0
  14. data/chef/instrumental_tools/templates/{instrumental.yml.erb → default/instrumental.yml.erb} +6 -6
  15. data/chef/omnibus.sh +27 -0
  16. data/conf/instrumental.yml +6 -6
  17. data/examples/README.md +1 -0
  18. data/examples/redis/README.md +10 -0
  19. data/examples/redis/redis_info.sh +10 -0
  20. data/ext/Rakefile +1 -0
  21. data/ext/mkrf_conf.rb +18 -0
  22. data/instrumental_tools.gemspec +8 -2
  23. data/lib/instrumental_tools/metric_script_executor.rb +45 -6
  24. data/lib/instrumental_tools/server_controller.rb +4 -1
  25. data/lib/instrumental_tools/system_inspector.rb +3 -0
  26. data/lib/instrumental_tools/system_inspector/win32.rb +85 -0
  27. data/lib/instrumental_tools/version.rb +1 -1
  28. data/test/integration/default/serverspec/instrumental_tools_spec.rb +32 -16
  29. data/win32/Makefile +18 -0
  30. data/win32/installer.nsis.erb +242 -0
  31. data/win32/logo.ico +0 -0
  32. data/win32/src/instrumental/InstrumentServerProcess.cs +147 -0
  33. data/win32/src/instrumental/InstrumentServerProcessWorker.cs +89 -0
  34. data/win32/src/instrumental/InstrumentServerService.cs +146 -0
  35. data/win32/src/instrumental/InstrumentServerServiceInstaller.cs +60 -0
  36. metadata +36 -7
Binary file
@@ -0,0 +1,147 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Diagnostics;
4
+
5
+
6
+
7
+ namespace Instrumental
8
+ {
9
+
10
+ class InstrumentServerProcess
11
+ {
12
+
13
+ private EventLog destination;
14
+ private Process process;
15
+
16
+ public InstrumentServerProcess(EventLog log){
17
+ destination = log;
18
+ }
19
+
20
+ public void Run(string executablePath, string configPath, string hostname, bool scriptsEnabled, string scriptsDirectory){
21
+ SetupProcess(executablePath, configPath, hostname, scriptsEnabled, scriptsDirectory);
22
+ }
23
+
24
+ public bool IsRunning(){
25
+ if(process == null){
26
+ return false;
27
+ }
28
+ try {
29
+ return !Convert.ToBoolean(process?.HasExited);
30
+ } catch(InvalidOperationException){
31
+ return false;
32
+ }
33
+ }
34
+
35
+ public void CleanupProcess(){
36
+ if(IsRunning()){
37
+ process.Kill();
38
+ process.WaitForExit();
39
+ }
40
+ process?.Close();
41
+ process = null;
42
+ }
43
+
44
+ public TimeSpan Age(){
45
+ DateTime lastStarted = process?.StartTime ?? DateTime.Now;
46
+ return DateTime.Now - lastStarted;
47
+ }
48
+
49
+ public string RubyDir(string basePath){
50
+ return basePath + "\\lib\\ruby";
51
+ }
52
+
53
+ public string RubyLibDir(string basePath){
54
+ return RubyDir(basePath) + "\\lib\\ruby";
55
+ }
56
+
57
+ public string AppDir(string basePath){
58
+ return basePath + "\\lib\\app";
59
+ }
60
+
61
+ public string VendorDir(string basePath){
62
+ return basePath + "\\lib\\vendor";
63
+ }
64
+
65
+ public string Gemfile(string basePath){
66
+ return VendorDir(basePath) + "\\Gemfile";
67
+ }
68
+
69
+ public string RubyExecutable(string basePath){
70
+ return RubyDir(basePath) + "\\bin.real\\ruby.exe";
71
+ }
72
+
73
+ public string InstrumentServerScript(string basePath){
74
+ return AppDir(basePath) + "\\bin\\instrument_server";
75
+ }
76
+
77
+ public string RubyFlags(){
78
+ return "-rbundler/setup";
79
+ }
80
+
81
+ public string SslCertFile(string basePath){
82
+ return RubyDir(basePath) + "\\lib\\ca-bundle.crt";
83
+ }
84
+
85
+ public void SetupProcess(string executablePath, string configPath, string hostname, bool scriptsEnabled, string scriptsDirectory){
86
+ CleanupProcess();
87
+ string args = $"{RubyFlags()} \"{InstrumentServerScript(executablePath)}\" -f \"{configPath}\" -H \"{hostname}\"";
88
+ if(scriptsEnabled){
89
+ args += $" -e -s \"{scriptsDirectory}\"";
90
+ }
91
+ args += " foreground";
92
+
93
+ string rubyVersion = "2.1.0";
94
+ string rubyArch = "i386-mingw32";
95
+ string rubyDir = RubyLibDir(executablePath);
96
+ string[] libDirs = new string[]{
97
+ $"{rubyDir}\\site_ruby\\{rubyVersion}",
98
+ $"{rubyDir}\\site_ruby\\{rubyVersion}\\{rubyArch}",
99
+ $"{rubyDir}\\site_ruby",
100
+ $"{rubyDir}\\vendor_ruby\\{rubyVersion}",
101
+ $"{rubyDir}\\vendor_ruby\\{rubyVersion}\\{rubyArch}",
102
+ $"{rubyDir}\\vendor_ruby",
103
+ $"{rubyDir}\\{rubyVersion}",
104
+ $"{rubyDir}\\{rubyVersion}\\{rubyArch}"
105
+ };
106
+
107
+ Dictionary<string, string> env = new Dictionary<string, string>(){
108
+ { "BUNDLE_GEMFILE", Gemfile(executablePath) },
109
+ { "RUBYLIB", String.Join(";", libDirs) },
110
+ { "SSL_CERT_FILE", SslCertFile(executablePath) }
111
+ };
112
+
113
+ destination.WriteEntry($"Trying to start {RubyExecutable(executablePath)} {args} with env {String.Join(";", env)}", EventLogEntryType.Information);
114
+
115
+ process = new Process();
116
+ process.StartInfo.FileName = RubyExecutable(executablePath);
117
+ process.StartInfo.Arguments = args;
118
+
119
+ foreach(KeyValuePair<string, string> entry in env){
120
+ process.StartInfo.EnvironmentVariables[entry.Key] = entry.Value;
121
+ }
122
+
123
+ process.StartInfo.UseShellExecute = false;
124
+ process.StartInfo.RedirectStandardOutput = true;
125
+ process.StartInfo.RedirectStandardError = true;
126
+ process.StartInfo.CreateNoWindow = true;
127
+ process.OutputDataReceived += new DataReceivedEventHandler((sender, e) => {
128
+ if(!String.IsNullOrEmpty(e.Data)){
129
+ destination.WriteEntry(e.Data, EventLogEntryType.Information);
130
+ }
131
+ });
132
+ process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => {
133
+ if(!String.IsNullOrEmpty(e.Data)){
134
+ destination.WriteEntry(e.Data, EventLogEntryType.Error);
135
+ }
136
+ });
137
+ if(!process.Start()){
138
+ destination.WriteEntry("Failed to start process", EventLogEntryType.Error);
139
+ } else {
140
+ process.BeginOutputReadLine();
141
+ process.BeginErrorReadLine();
142
+ }
143
+ }
144
+
145
+
146
+ }
147
+ }
@@ -0,0 +1,89 @@
1
+ using System;
2
+ using System.Diagnostics;
3
+ using System.Threading;
4
+
5
+ namespace Instrumental
6
+ {
7
+
8
+ class InstrumentServerProcessWorker
9
+ {
10
+
11
+ private volatile bool doRun;
12
+ private string executablePath;
13
+ private string configPath;
14
+ private string hostname;
15
+ private bool scriptsEnabled;
16
+ private string scriptsDirectory;
17
+ private EventLog destination;
18
+ private Thread runnerThread;
19
+ private TimeSpan checkInterval;
20
+ private int retryFallback;
21
+ private int retries;
22
+ private int maxFallbackMult;
23
+
24
+ public InstrumentServerProcessWorker(EventLog log, string execPath, string confPath, string host, bool enableScripts, string scriptDir){
25
+ destination = log;
26
+ executablePath = execPath;
27
+ configPath = confPath;
28
+ hostname = host;
29
+ scriptsEnabled = enableScripts;
30
+ scriptsDirectory = scriptDir;
31
+ runnerThread = null;
32
+ doRun = false;
33
+ checkInterval = TimeSpan.FromSeconds(5);
34
+ retryFallback = 7; // Seconds
35
+ maxFallbackMult = 3; // Seconds
36
+ retries = -1;
37
+ }
38
+
39
+ public void Run(){
40
+ destination.WriteEntry("Starting worker thread", EventLogEntryType.Information);
41
+ InstrumentServerProcess process = new InstrumentServerProcess(destination);
42
+ try {
43
+ process.Run(executablePath, configPath, hostname, scriptsEnabled, scriptsDirectory);
44
+ } catch (Exception ex) {
45
+ destination.WriteEntry("Exception trying to start, " + ex.Message, EventLogEntryType.Error);
46
+ }
47
+ DateTime lastIter = DateTime.Now;
48
+ DateTime lastExec = lastIter;
49
+ while(doRun){
50
+ Thread.Sleep(checkInterval);
51
+ try {
52
+ if(!process.IsRunning()){
53
+ if( (DateTime.Now - lastExec) > TimeSpan.FromSeconds(Math.Pow(retryFallback, retries))){
54
+ destination.WriteEntry("Attempting to start process", EventLogEntryType.Information);
55
+ process.Run(executablePath, configPath, hostname, scriptsEnabled, scriptsDirectory);
56
+ lastExec = DateTime.Now;
57
+ retries = Math.Min(retries + 1, maxFallbackMult);
58
+ }
59
+ }
60
+ } catch (Exception ex) {
61
+ destination.WriteEntry("Exception trying to start, " + ex.Message, EventLogEntryType.Error);
62
+ retries = Math.Min(retries + 1, maxFallbackMult);
63
+ }
64
+ }
65
+ process.CleanupProcess();
66
+ destination.WriteEntry("Worker thread ended", EventLogEntryType.Information);
67
+ }
68
+
69
+ public void RequestStop(){
70
+ doRun = false;
71
+ }
72
+
73
+ public void Stop(){
74
+ RequestStop();
75
+ runnerThread?.Join();
76
+ runnerThread = null;
77
+ }
78
+
79
+ public void Start(){
80
+ if(Convert.ToBoolean(runnerThread?.IsAlive)){
81
+ Stop();
82
+ }
83
+ doRun = true;
84
+ runnerThread = new Thread(this.Run);
85
+ runnerThread.Start();
86
+ }
87
+
88
+ }
89
+ }
@@ -0,0 +1,146 @@
1
+ using Microsoft.Win32;
2
+
3
+ using System;
4
+ using System.Diagnostics;
5
+ using System.IO;
6
+ using System.Reflection;
7
+ using System.ServiceProcess;
8
+ using System.Threading;
9
+
10
+ namespace Instrumental
11
+ {
12
+ class InstrumentServerService : ServiceBase
13
+ {
14
+ public const string NameOfService = "Instrument Server";
15
+ public const string PathKey = "Path";
16
+ public const string ConfigKey = "Config";
17
+ public const string HostnameKey = "Hostname";
18
+ public const string EnableScriptsKey = "ScriptsEnabled";
19
+ public const string ScriptsDirectoryKey = "ScriptsDirectory";
20
+ public const string InstrumentalKey = "Instrumental";
21
+
22
+ private InstrumentServerProcessWorker ProcessWorker;
23
+
24
+ public InstrumentServerService()
25
+ {
26
+ this.ServiceName = InstrumentServerService.NameOfService;
27
+
28
+ this.EventLog.Log = "Application";
29
+
30
+ this.CanHandlePowerEvent = false;
31
+ this.CanHandleSessionChangeEvent = false;
32
+ this.CanPauseAndContinue = false;
33
+ this.CanShutdown = false;
34
+ this.CanStop = true;
35
+
36
+ string path = BasePath() ?? DefaultBasePath();
37
+ EventLog.WriteEntry($"Starting with path {BasePath()} (Default: {DefaultBasePath()}), config {Config()} (Default: {DefaultConfig()}), hostname {Hostname()} (Default: {DefaultHostname()}), scripts enabled {ScriptsEnabled()}, scripts directory {ScriptsDirectory()} (Default: {DefaultScriptsDirectory()}), values taken from {InstrumentalRegistryKey()}", EventLogEntryType.Information);
38
+
39
+ this.ProcessWorker = new InstrumentServerProcessWorker(EventLog,
40
+ path,
41
+ Config() ?? DefaultConfig(),
42
+ Hostname() ?? DefaultHostname(),
43
+ ScriptsEnabled(),
44
+ ScriptsDirectory() ?? DefaultScriptsDirectory());
45
+ }
46
+
47
+ static void Main()
48
+ {
49
+ ServiceBase.Run(new InstrumentServerService());
50
+ }
51
+
52
+ public static RegistryKey InstrumentalRegistryKey(bool withWriteAccess = false){
53
+ RegistryKey localKey;
54
+ if(Environment.Is64BitOperatingSystem){
55
+ localKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
56
+ } else {
57
+ localKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
58
+ }
59
+ if(withWriteAccess){
60
+ return localKey.OpenSubKey("SOFTWARE", true).CreateSubKey(InstrumentalKey);
61
+ } else {
62
+ return localKey.OpenSubKey("SOFTWARE").OpenSubKey(InstrumentalKey);
63
+ }
64
+ }
65
+
66
+ public static string AssemblyDirectory()
67
+ {
68
+ string codeBase = Assembly.GetExecutingAssembly().CodeBase;
69
+ UriBuilder uri = new UriBuilder(codeBase);
70
+ string assemblyPath = Uri.UnescapeDataString(uri.Path);
71
+ return Path.GetDirectoryName(assemblyPath);
72
+ }
73
+
74
+ public static string StringFromRegistryKey(string keyName){
75
+ RegistryKey regkey = InstrumentalRegistryKey();
76
+ if(regkey != null){
77
+ return Convert.ToString(regkey.GetValue(keyName));
78
+ } else {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ public static string BasePath() {
84
+ return StringFromRegistryKey(PathKey);
85
+ }
86
+
87
+ public static string DefaultBasePath() {
88
+ return InstrumentServerService.AssemblyDirectory();
89
+ }
90
+
91
+ public static string Config() {
92
+ return StringFromRegistryKey(ConfigKey);
93
+ }
94
+
95
+ public static string DefaultConfig() {
96
+ return DefaultBasePath() + "\\etc\\instrumental.yml";
97
+ }
98
+
99
+ public static string Hostname(){
100
+ return StringFromRegistryKey(HostnameKey);
101
+ }
102
+
103
+ public static string DefaultHostname(){
104
+ return System.Environment.MachineName;
105
+ }
106
+
107
+ public static bool ScriptsEnabled() {
108
+ object value = InstrumentalRegistryKey()?.GetValue(EnableScriptsKey);
109
+ if(value != null){
110
+ return Convert.ToBoolean(value);
111
+ } else {
112
+ return DefaultScriptsEnabled();
113
+ }
114
+ }
115
+
116
+ public static bool DefaultScriptsEnabled(){
117
+ return false;
118
+ }
119
+
120
+ public static string ScriptsDirectory() {
121
+ return StringFromRegistryKey(ScriptsDirectoryKey);
122
+ }
123
+
124
+ public static string DefaultScriptsDirectory(){
125
+ return DefaultBasePath() + "\\Scripts";
126
+ }
127
+
128
+ protected override void Dispose(bool disposing)
129
+ {
130
+ base.Dispose(disposing);
131
+ }
132
+
133
+ protected override void OnStart(string[] args)
134
+ {
135
+ base.OnStart(args);
136
+ ProcessWorker.Start();
137
+ }
138
+
139
+ protected override void OnStop()
140
+ {
141
+ base.OnStop();
142
+ ProcessWorker.Stop();
143
+ }
144
+
145
+ }
146
+ }
@@ -0,0 +1,60 @@
1
+ using Microsoft.Win32;
2
+
3
+ using System;
4
+ using System.Collections;
5
+ using System.ComponentModel;
6
+ using System.Configuration.Install;
7
+ using System.ServiceProcess;
8
+
9
+ namespace Instrumental
10
+ {
11
+ [RunInstaller(true)]
12
+ public class InstrumentServerServiceInstaller : Installer
13
+ {
14
+
15
+ public const string PathArgument = "Path";
16
+ public const string ConfigArgument = "Config";
17
+ public const string HostnameArgument = "Hostname";
18
+ public const string ScriptsDirectoryArgument = "ScriptsDirectory";
19
+ public const string ScriptsEnabledArgument = "ScriptsEnabled";
20
+
21
+ public InstrumentServerServiceInstaller()
22
+ {
23
+ ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
24
+ ServiceInstaller serviceInstaller = new ServiceInstaller();
25
+
26
+ serviceProcessInstaller.Account = ServiceAccount.LocalService;
27
+ serviceProcessInstaller.Username = null;
28
+ serviceProcessInstaller.Password = null;
29
+
30
+ serviceInstaller.DisplayName = InstrumentServerService.NameOfService;
31
+ serviceInstaller.StartType = ServiceStartMode.Automatic;
32
+ serviceInstaller.ServiceName = InstrumentServerService.NameOfService;
33
+
34
+ this.Installers.Add(serviceProcessInstaller);
35
+ this.Installers.Add(serviceInstaller);
36
+ }
37
+
38
+ public override void Install(IDictionary savedState)
39
+ {
40
+ base.Install(savedState);
41
+
42
+ RegistryKey AppKey = InstrumentServerService.InstrumentalRegistryKey(true);
43
+ string Path = Context.Parameters[PathArgument] ?? InstrumentServerService.DefaultBasePath();
44
+ string Config = Context.Parameters[ConfigArgument] ?? InstrumentServerService.DefaultConfig();
45
+ string ScriptDir = Context.Parameters[ScriptsDirectoryArgument] ?? InstrumentServerService.DefaultScriptsDirectory();
46
+ string Hostname = Context.Parameters[HostnameArgument] ?? InstrumentServerService.DefaultHostname();
47
+
48
+ bool EnableScripts = InstrumentServerService.DefaultScriptsEnabled();
49
+ if(Context.Parameters[ScriptsEnabledArgument] != null){
50
+ EnableScripts = Convert.ToBoolean(Context.Parameters[ScriptsEnabledArgument]);
51
+ }
52
+
53
+ AppKey.SetValue(InstrumentServerService.PathKey, Path);
54
+ AppKey.SetValue(InstrumentServerService.ConfigKey, Config);
55
+ AppKey.SetValue(InstrumentServerService.EnableScriptsKey, EnableScripts);
56
+ AppKey.SetValue(InstrumentServerService.ScriptsDirectoryKey, ScriptDir);
57
+ AppKey.SetValue(InstrumentServerService.HostnameKey, Hostname);
58
+ }
59
+ }
60
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: instrumental_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Expected Behavior
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-21 00:00:00.000000000 Z
11
+ date: 2015-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: instrumental_agent
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.12.6
19
+ version: 0.13.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.12.6
26
+ version: 0.13.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pidly
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -178,13 +178,28 @@ dependencies:
178
178
  - - ">="
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: winrm-transport
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '1.0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '1.0'
181
195
  description: A collection of scripts useful for monitoring servers and services with
182
196
  Instrumental (instrumentalapp.com)
183
197
  email:
184
198
  - support@instrumentalapp.com
185
199
  executables:
186
200
  - instrument_server
187
- extensions: []
201
+ extensions:
202
+ - ext/mkrf_conf.rb
188
203
  extra_rdoc_files: []
189
204
  files:
190
205
  - BUILD.md
@@ -203,7 +218,9 @@ files:
203
218
  - chef/instrumental_tools/attributes/default.rb
204
219
  - chef/instrumental_tools/metadata.rb
205
220
  - chef/instrumental_tools/recipes/default.rb
206
- - chef/instrumental_tools/templates/instrumental.yml.erb
221
+ - chef/instrumental_tools/templates/default/instrument_server.erb
222
+ - chef/instrumental_tools/templates/default/instrumental.yml.erb
223
+ - chef/omnibus.sh
207
224
  - conf/instrumental.yml
208
225
  - debian/after-install.sh
209
226
  - debian/after-remove.sh
@@ -216,6 +233,10 @@ files:
216
233
  - examples/mongo/mongo_3.rb
217
234
  - examples/mysql/README.md
218
235
  - examples/mysql/mysql_status.rb
236
+ - examples/redis/README.md
237
+ - examples/redis/redis_info.sh
238
+ - ext/Rakefile
239
+ - ext/mkrf_conf.rb
219
240
  - instrumental_tools.gemspec
220
241
  - lib/instrumental_tools/capistrano.rb
221
242
  - lib/instrumental_tools/metric_script_executor.rb
@@ -223,6 +244,7 @@ files:
223
244
  - lib/instrumental_tools/system_inspector.rb
224
245
  - lib/instrumental_tools/system_inspector/linux.rb
225
246
  - lib/instrumental_tools/system_inspector/osx.rb
247
+ - lib/instrumental_tools/system_inspector/win32.rb
226
248
  - lib/instrumental_tools/version.rb
227
249
  - puppet/.kitchen.yml
228
250
  - puppet/.librarian/puppet/config
@@ -238,6 +260,13 @@ files:
238
260
  - rpm/instrument_server
239
261
  - systemd/instrument_server.service
240
262
  - test/integration/default/serverspec/instrumental_tools_spec.rb
263
+ - win32/Makefile
264
+ - win32/installer.nsis.erb
265
+ - win32/logo.ico
266
+ - win32/src/instrumental/InstrumentServerProcess.cs
267
+ - win32/src/instrumental/InstrumentServerProcessWorker.cs
268
+ - win32/src/instrumental/InstrumentServerService.cs
269
+ - win32/src/instrumental/InstrumentServerServiceInstaller.cs
241
270
  homepage: http://github.com/expectedbehavior/instrumental_tools
242
271
  licenses:
243
272
  - MIT
@@ -258,7 +287,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
258
287
  version: '0'
259
288
  requirements: []
260
289
  rubyforge_project:
261
- rubygems_version: 2.2.2
290
+ rubygems_version: 2.4.8
262
291
  signing_key:
263
292
  specification_version: 4
264
293
  summary: Command line tools for Instrumental