sensible-cli 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4841010ea567e04f3f358f1f98b9e61aedb419c6f6857f5f2644bc0d9c5d58e3
4
- data.tar.gz: 16b5ce3ead9edfd3ff17465f8cb4d5c9463ef5bf14e429e02ab8b70f249921c7
3
+ metadata.gz: ac1a27c2c7bb7b0b94e321c73538dc2c86acd7fff5a1f08df6c8c8c4457d3312
4
+ data.tar.gz: 6c09a5676d85d4a19985f9f84a88e9b49f5f00ed760b034f60b7c642a49d47df
5
5
  SHA512:
6
- metadata.gz: dd36abd65d68819f83f817aa4979bda0ad78aa4b173e42c1792500812ac99501d5d0aaccac02d3d84da2c636338769adc41fcba7453266dbbf1b36efc0763b42
7
- data.tar.gz: 5b1e16c926d168f4498e440161c294587428562b1a8672e2bf7606f111219c32e12fd0c5719e96141530698ae9e8c4cd8d3554aca15c65aebf16a6dffa296f90
6
+ metadata.gz: 471fd3d26546e3c722a550f4f164502ef7ec71a23dada9cb67fd882d8f84388478ed1120f59b3a5ae9f2e543cc7941b39270d2ddba4472e25951f78576ccec2a
7
+ data.tar.gz: ec7ee80deebbce2917b4f6801e885dd3802f9e90d3e2d1c841ea651b5f34c6f4f48791cd8658f046205a2ddbc8a26d159f4686dd6460cb5e16fb0a9ffe1893f1
data/.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="MaterialThemeProjectNewConfig">
4
+ <option name="metadata">
5
+ <MTProjectMetadataState>
6
+ <option name="userId" value="-7c4bb857:19787ef65ab:-7fff" />
7
+ </MTProjectMetadataState>
8
+ </option>
9
+ </component>
10
+ </project>
data/.idea/misc.xml ADDED
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="RVM: ruby-3.4.2 [global]" project-jdk-type="RUBY_SDK" />
4
+ </project>
data/.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/sensible-ruby.iml" filepath="$PROJECT_DIR$/.idea/sensible-ruby.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="RUBY_MODULE" version="4">
3
+ <component name="ModuleRunConfigurationManager">
4
+ <shared />
5
+ </component>
6
+ <component name="NewModuleRootManager">
7
+ <content url="file://$MODULE_DIR$">
8
+ <sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
9
+ <sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
10
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
11
+ </content>
12
+ <orderEntry type="inheritedJdk" />
13
+ <orderEntry type="sourceFolder" forTests="false" />
14
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v2.6.9, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
15
+ <orderEntry type="library" scope="PROVIDED" name="constrain (v0.10.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
16
+ <orderEntry type="library" scope="PROVIDED" name="date (v3.4.1, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.6.2, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
18
+ <orderEntry type="library" scope="PROVIDED" name="erb (v5.0.1, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
19
+ <orderEntry type="library" scope="PROVIDED" name="forward_to (v0.3.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
20
+ <orderEntry type="library" scope="PROVIDED" name="indented_io (v0.9.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="io-console (v0.8.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
22
+ <orderEntry type="library" scope="PROVIDED" name="irb (v1.15.2, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="pastel (v0.8.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
24
+ <orderEntry type="library" scope="PROVIDED" name="pp (v0.6.2, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
25
+ <orderEntry type="library" scope="PROVIDED" name="prettyprint (v0.2.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
26
+ <orderEntry type="library" scope="PROVIDED" name="psych (v5.2.6, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
27
+ <orderEntry type="library" scope="PROVIDED" name="rake (v13.3.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="rdoc (v6.14.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
29
+ <orderEntry type="library" scope="PROVIDED" name="reline (v0.6.1, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
30
+ <orderEntry type="library" scope="PROVIDED" name="rspec (v3.13.1, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.13.4, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
32
+ <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.13.5, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
33
+ <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.13.5, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
34
+ <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.13.4, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
35
+ <orderEntry type="library" scope="PROVIDED" name="shellopts (v2.6.2, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
36
+ <orderEntry type="library" scope="PROVIDED" name="stringio (v3.1.7, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
37
+ <orderEntry type="library" scope="PROVIDED" name="tty-color (v0.6.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
38
+ <orderEntry type="library" scope="PROVIDED" name="tty-command (v0.10.1, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
39
+ <orderEntry type="library" scope="PROVIDED" name="tty-cursor (v0.7.1, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
40
+ <orderEntry type="library" scope="PROVIDED" name="tty-prompt (v0.23.1, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
41
+ <orderEntry type="library" scope="PROVIDED" name="tty-reader (v0.9.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
42
+ <orderEntry type="library" scope="PROVIDED" name="tty-screen (v0.8.2, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
43
+ <orderEntry type="library" scope="PROVIDED" name="tty-spinner (v0.9.3, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
44
+ <orderEntry type="library" scope="PROVIDED" name="tty-which (v0.5.0, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
45
+ <orderEntry type="library" scope="PROVIDED" name="wisper (v2.0.1, RVM: ruby-3.4.2 [global]) [gem]" level="application" />
46
+ </component>
47
+ </module>
data/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
data/README.md CHANGED
@@ -208,3 +208,9 @@ An example of a command that sets the environment to `prod`:
208
208
  sensible check --env prod # Check the project for missing dependencies in the prod environment
209
209
  sensible install --env stage # Install missing dependencies and requirements in the staging environment
210
210
  ```
211
+
212
+
213
+ # Todo
214
+ [ ] Add --host support, to run it agains another machine using ssh
215
+ [ ] Variables for tasks
216
+ [ ] Include other tasks?
data/exe/sensible CHANGED
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env -S ruby --yjit
2
2
 
3
3
  SPEC = %(
4
- @ A small tool to manage and deploy projects
4
+ @ A small tool to manage and deploy projects/machines
5
5
 
6
6
  Install and update systems much like ansible but using shell scripts. It uses
7
7
  a sensible.yml configuration file to control the process
@@ -10,14 +10,19 @@ SPEC = %(
10
10
  -e,env,environment=ENV
11
11
  Sets the environment (dev/prod/etc),
12
12
 
13
- -f,file=FILE
13
+ -f,file=CONF:EFILE
14
14
  Path to sensible configuration file
15
15
 
16
- -d,dir,directory=DIR
16
+ -d,dir,directory=EDIR
17
17
  Path to sensible directory. Default '.sensible'
18
18
 
19
+ --host=HOST
20
+ Run tasks agains a remote machine over ssh. Requires ssh keys added to machine.
21
+
19
22
  -v,verbose
20
- Verbose output
23
+ Verbose
24
+
25
+ -a=EFILE
21
26
 
22
27
  COMMANDS
23
28
  check!
@@ -41,17 +46,9 @@ SPEC = %(
41
46
 
42
47
  require_relative '../lib/sensible.rb'
43
48
  require 'shellopts'
49
+ require_relative '../lib/sensible/log'
44
50
 
45
- # Monkey patch where version_number is not fetched correctly in ruby 3.4.2
46
- module ShellOpts
47
- class ShellOpts
48
- def version_number
49
- Sensible::VERSION
50
- end
51
- end
52
- end
53
-
54
- opts, args = ShellOpts::process(SPEC, ARGV)
51
+ opts, args = ShellOpts::process(SPEC, ARGV, version_number: Sensible::VERSION)
55
52
  cmd = opts.subcommand!
56
53
  #cmd == :task! or args.expect(0) # Ensure no arguments except for task
57
54
 
@@ -60,31 +57,35 @@ if opts.verbose
60
57
  puts " env: #{opts.env}"
61
58
  puts " file: #{opts.file}"
62
59
  puts " dir: #{opts.dir}"
60
+ puts " a: #{opts.a}"
63
61
  puts " args: #{args}"
64
- # puts "Command: #{cmd.to_s.sub(/!$/, "")}"
65
62
  end
66
63
 
64
+ if opts.subcommand == :init!
65
+ Sensible::Sensible.init(opts)
66
+ exit
67
+ end
68
+
69
+ sensible = Sensible::Sensible.new("sensible.yml", opts, args)
67
70
  case opts.subcommand
68
- when :init!
69
- Sensible::Sensible.init(opts)
70
- else
71
- sensible = Sensible::Sensible.new("sensible.yml", opts, args)
72
-
73
- case opts.subcommand
74
- when :check!
75
- sensible.check
76
- when :install!
77
- sensible.install
78
- when :task!
79
- case cmd.subcommand
80
- when :list!; sensible.task_list
81
- when :run!
82
- arg = args.expect(1) # Expect a single argument to sensible task
83
- sensible.task_run(arg)
84
- when :create!
85
- arg = args.expect(1) # Expect a single argument to sensible task
86
- sensible.task_create(arg)
87
- end
71
+ when :check!
72
+ sensible.check
73
+ when :install!
74
+ sensible.install
75
+ when :task!
76
+ case opts.subcommand!.subcommand
77
+ when nil
78
+ Sensible::Logger::error "Missing subcommand"
79
+ when :list!; sensible.task_list
80
+ when :run!
81
+ arg = args.expect(1) # Expect a single argument to sensible task
82
+ sensible.task_run(arg)
83
+ when :create!
84
+ arg = args.expect(1) # Expect a single argument to sensible task
85
+ sensible.task_create(arg)
88
86
  end
87
+ else
88
+ ShellOpts::error "Missing command"
89
89
  end
90
90
 
91
+
data/lib/sensible/log.rb CHANGED
@@ -7,7 +7,14 @@ module Sensible
7
7
 
8
8
  class Logger
9
9
  def self.log(message, indent = 0, use_print: false)
10
- puts message
10
+ spaceIndent = ""
11
+ indent.times { |i| spaceIndent << " " }
12
+
13
+ if use_print
14
+ print "#{spaceIndent}#{message}"
15
+ else
16
+ puts "#{spaceIndent}#{message}"
17
+ end
11
18
  end
12
19
 
13
20
  def self.success(message, indent = 0, use_print: false)
@@ -64,5 +71,13 @@ module Sensible
64
71
 
65
72
  prompt.yes?("#{spaceIndent}#{message}")
66
73
  end
74
+
75
+ def self.clear_line()
76
+ print "\r\e[K"
77
+ end
78
+
79
+ def self.flush
80
+ $stdout.flush
81
+ end
67
82
  end
68
83
  end
@@ -1,4 +1,5 @@
1
1
  require_relative 'log'
2
+ require_relative 'shell'
2
3
  require 'tty-prompt'
3
4
  require 'pastel'
4
5
 
@@ -6,47 +7,57 @@ module Sensible
6
7
  class Package
7
8
  attr_reader :sensible
8
9
  attr_reader :name
9
- attr_reader :check
10
- attr_reader :install
11
- attr_reader :env
12
-
13
- def initialize(packageHash, sensible)
14
- @name = packageHash['name']
15
- @check = packageHash['check']
16
- @install = packageHash['install']
17
- @env = packageHash['env'] || []
10
+ attr_reader :distro
18
11
 
12
+ def initialize(packageName, distro, sensible)
13
+ @name = packageName
14
+ @distro = distro
19
15
  @sensible = sensible
20
16
  end
21
17
 
22
-
23
18
  # Check if the package is installed
24
19
  def do_check
25
- if @check
26
- result = `#{@check}`
27
- return $?.success?
28
- else
29
- # If check is not set, then infer that it's a system package
30
- result = `rpm -q #{@name}`
20
+ check_command = nil
21
+
22
+ case @distro
23
+ when "rpm"
24
+ check_command = "rpm -q #{name}"
25
+ when "deb"
26
+ check_command = "dpkg-query -W #{name}"
27
+ when "aur"
28
+ check_command = "pacman -Qi #{name}"
29
+ end
31
30
 
32
- if result.include? 'is not installed'
33
- return false
34
- else
35
- return true
36
- end
31
+ if check_command == nil
32
+ Logger.error("Unknown check command")
33
+ exit(1)
37
34
  end
38
-
35
+
36
+ # Run the shell command
37
+ shell = Shell.new(@sensible)
38
+ return shell.run_command(check_command)
39
39
  end
40
40
 
41
41
  # Install the package
42
42
  def do_install
43
- if @install
44
- system(@install, out: File::NULL)
45
- return $?.success?
46
- else
47
- system("sudo", "dnf", "install", "-y", @name, out: File::NULL, err: File::NULL)
48
- return $?.success?
43
+ install_command = nil
44
+
45
+ case @distro
46
+ when "rpm"
47
+ install_command = "sudo dnf install -y #{name}"
48
+ when "deb"
49
+ install_command = "sudo apt get install -y #{name}"
50
+ when "aur"
51
+ install_command = "pacman -Syu #{name}"
52
+ end
53
+
54
+ if install_command == nil
55
+ Logger.error("Unknown install command")
56
+ exit(1)
49
57
  end
50
- end
58
+
59
+ shell = Shell.new(@sensible)
60
+ return shell.run_command(install_command)
61
+ end
51
62
  end
52
63
  end
@@ -5,7 +5,7 @@ require_relative 'package'
5
5
  module Sensible
6
6
  class Parse
7
7
 
8
- # Parse the package list from sensible.yml
8
+ # Parse the package list
9
9
  def self.parse_sensible_packages(sensible_hash_list, sensible)
10
10
  list = []
11
11
  for pkg in sensible_hash_list
@@ -1,7 +1,77 @@
1
1
  require_relative 'package'
2
+ require_relative 'parse'
2
3
 
3
4
  module Sensible
4
- class Requirement < Package
5
+ class Requirement
6
+ attr_reader :sensible
7
+ attr_reader :name
8
+ attr_reader :check
9
+ attr_reader :install
10
+ attr_reader :env
11
+ attr_reader :description
12
+ attr_reader :packages
5
13
  # Pretty much the same as package right now
14
+
15
+ def initialize(requirement_file_name, sensible)
16
+ requirement_file_path = "#{sensible.sensible_folder}/#{sensible.requirements_folder}/#{requirement_file_name}.yml"
17
+
18
+ # If requirement is not found, exit!
19
+ if not File.exist?(requirement_file_path)
20
+ pastel = Pastel.new
21
+ Logger.error("Requirement: #{pastel.bold(requirement_file_name)} does not exist!")
22
+ exit(1)
23
+ end
24
+
25
+ # Load the data
26
+ requirement_data = YAML.load_file(requirement_file_path)
27
+
28
+ @sensible = sensible
29
+ @name = requirement_data["name"]
30
+ @check = requirement_data["check"]
31
+ @install = requirement_data["install"]
32
+ @env = requirement_data["env"]
33
+ @description = requirement_data["description"]
34
+
35
+ # Parse packages
36
+ @packages = Parse::parse_sensible_packages(requirement_data['packages'], sensible)
37
+ end
38
+
39
+ def do_check
40
+ # If check is not set, always run the task
41
+ if @check == nil
42
+ return false
43
+ end
44
+
45
+ # If there is no check, default to false, to force task to install every time
46
+ system(@check, out: File::NULL)
47
+ return $?.success?
48
+ end
49
+
50
+ def do_install
51
+ # TODO: Handle the show output property!
52
+
53
+ if @install.include?("\n")
54
+ temp_path = "/tmp/sensible"
55
+ temp_file_name = "install.sh"
56
+ temp_file_path = "#{temp_path}/#{temp_file_name}"
57
+
58
+ # Make sure we have the tmp folder created
59
+ FileUtils.mkdir_p(temp_path)
60
+
61
+ File.open(temp_file_path, "w") do |f|
62
+ f.puts "#!/usr/bin/env bash\n\n"
63
+ f.write(@install)
64
+ end
65
+
66
+ # Make it executable
67
+ File.chmod(0700, temp_file_path)
68
+
69
+ system("#{temp_file_path}", out: File::NULL, err: File::NULL)
70
+ return $?.success?
71
+ else
72
+ system(@install, out: File::NULL, err: File::NULL)
73
+ return $?.success?
74
+ end
75
+ end
6
76
  end
7
77
  end
@@ -0,0 +1,106 @@
1
+ require 'net/ssh'
2
+
3
+ module Sensible
4
+ class Shell
5
+ attr_reader :sensible
6
+
7
+ def initialize(sensible)
8
+ @sensible = sensible
9
+ end
10
+
11
+ def run_command(command, user = nil, show_output: false)
12
+ show_output = @sensible.opts.verbose
13
+
14
+ if (@sensible.opts.host != nil)
15
+ # Run command on remote machine
16
+ Net::SSH.start(@sensible.opts.host, 'root') do |ssh|
17
+ out, err, code = exec_user_with_status(ssh, command, as_user: user)
18
+
19
+ if @sensible.opts.verbose
20
+ puts "STDOUT: #{out}"
21
+ puts "STDERR: #{err}"
22
+ puts "EXIT CODE: #{code}"
23
+ end
24
+
25
+ # Show a warning if the user does not exist!
26
+ if err.include?("does not exist or the user entry does not contain all the required fields")
27
+ Logger.error("User: '#{user}' does not exist!")
28
+ end
29
+
30
+
31
+ if code == 0
32
+ return true
33
+ else
34
+ return false
35
+ end
36
+ end
37
+ else
38
+ # Standard local shell command
39
+ # If command contains new lines, use a temporary file
40
+ if command.include?("\n")
41
+ system("bash", "-c", command, out: (show_output ? $stdout : File::NULL), err: (show_output ? $stderr : File::NULL))
42
+ return $?.success?
43
+ else
44
+ if user != nil
45
+ system("sudo -u #{user} #{command}", out: (show_output ? $stdout : File::NULL), err: (show_output ? $stderr : File::NULL))
46
+ else
47
+ system(command, out: (show_output ? $stdout : File::NULL), err: (show_output ? $stderr : File::NULL))
48
+ end
49
+ return $?.success?
50
+ end
51
+ end
52
+ end
53
+
54
+ def exec_with_status(ssh, command)
55
+ stdout = stderr = ""
56
+ exit_code = nil
57
+
58
+ ssh.open_channel do |ch|
59
+ ch.exec(command) do |ch, success|
60
+ raise "could not exec #{command}" unless success
61
+
62
+ ch.on_data { |c, d| stdout << d }
63
+ ch.on_extended_data { |c, t, d| stderr << d }
64
+ ch.on_request("exit-status") { |c, data| exit_code = data.read_long }
65
+ end
66
+ end
67
+ ssh.loop
68
+ [stdout, stderr, exit_code]
69
+ end
70
+
71
+ def exec_user_with_status(ssh, command, as_user: nil)
72
+ stdout = ''
73
+ stderr = ''
74
+ exit_code = nil
75
+
76
+ ssh.open_channel do |ch|
77
+
78
+ if as_user
79
+ ch.exec("su - #{as_user}") do |ch_exec, success|
80
+ raise "could not exec sudo" unless success
81
+ ch.send_data("#{command}\n")
82
+
83
+ ch.on_data { |_, data| stdout << data }
84
+ ch.on_extended_data { |_, _, data| stderr << data }
85
+ ch.on_request("exit-status") { |_, data| exit_code = data.read_long }
86
+
87
+ # when done:
88
+ ch.send_data("exit\n")
89
+ end
90
+ else
91
+ ch.exec(command) do |ch_exec, success|
92
+ raise "Could not exec #{cmd}" unless success
93
+
94
+ ch.on_data { |_, data| stdout << data }
95
+ ch.on_extended_data { |_, _, data| stderr << data }
96
+ ch.on_request("exit-status") { |_, data| exit_code = data.read_long }
97
+ end
98
+ end
99
+ end
100
+
101
+ ssh.loop
102
+ [stdout, stderr, exit_code]
103
+ end
104
+
105
+ end
106
+ end
data/lib/sensible/task.rb CHANGED
@@ -1,66 +1,98 @@
1
1
  require_relative 'package'
2
+ require_relative 'shell'
2
3
 
3
4
  module Sensible
4
- class Task < Package
5
+ class Task
6
+ attr_reader :sensible
7
+ attr_reader :name
8
+ attr_reader :packages
9
+ attr_reader :check
10
+ attr_reader :script
11
+ attr_reader :require
12
+ attr_reader :env
5
13
  attr_reader :file_name
14
+ attr_reader :user
6
15
  attr_reader :description
7
16
  attr_accessor :show_output
8
17
 
9
- def initialize(taskHash, file_name, sensible)
10
- super(taskHash, sensible)
18
+ def initialize(taskHash, file_name, user = nil, sensible)
19
+ @name = taskHash['name']
20
+ @check = taskHash['check']
21
+ @script = taskHash['script']
22
+ @require = taskHash['require']
23
+ @env = taskHash['env'] || []
11
24
  @file_name = file_name
12
25
  @description = taskHash['description']
13
26
  @show_output = taskHash['showOutput']
14
- end
15
-
16
- def do_check
17
- do_verify()
18
-
19
- # If check is not set, always run the task
20
- if @check == nil
21
- return false
27
+
28
+ # If task is initalized with user, force use of that
29
+ if user != nil
30
+ @user = user
31
+ end
32
+
33
+ # Always use the user from the task
34
+ if taskHash['user'] != nil
35
+ @user = taskHash['user']
22
36
  end
23
37
 
24
- # If there is no check, default to false, to force task to install every time
25
- system(@check, out: File::NULL)
26
- return $?.success?
27
- end
28
-
29
- def do_install
30
- # TODO: Handle the show output property!
38
+ @sensible = sensible
39
+
40
+ @packages = []
41
+ if taskHash['packages']
42
+ taskHash['packages'].each do |distro, packages|
43
+ packages.each do |packageName|
44
+ @packages << Package.new(packageName, distro, sensible)
45
+ end
46
+ end
47
+ end
48
+ end
31
49
 
32
- if @install.include?("\n")
33
- temp_path = "/tmp/sensible"
34
- temp_file_name = "install.sh"
35
- temp_file_path = "#{temp_path}/#{temp_file_name}"
50
+ # Check if the packages in this task is installed
51
+ def do_packages_check
52
+ has_all_packages_install = true
36
53
 
37
- # Make sure we have the tmp folder created
38
- FileUtils.mkdir_p(temp_path)
54
+ @packages.each do |package|
55
+ package.name
39
56
 
40
- File.open(temp_file_path, "w") do |f|
41
- f.puts "#!/usr/bin/env bash\n\n"
42
- f.write(@install)
57
+ if !package.do_check
58
+ has_all_packages_install = false
43
59
  end
60
+ end
61
+
62
+ return has_all_packages_install
63
+ end
44
64
 
45
- # Make it executable
46
- File.chmod(0700, temp_file_path)
65
+ # Install the packages in this task
66
+ def do_packages_install
67
+ has_all_packages_install = true
47
68
 
48
- system("#{temp_file_path}", out: File::NULL)
49
- return $?.success?
50
- else
51
- system(@install, out: File::NULL)
52
- return $?.success?
69
+ @packages.each do |package|
70
+ if !package.do_install
71
+ has_all_packages_install = false
72
+ end
53
73
  end
54
- end
55
74
 
56
- def do_verify
57
- if !@install
58
- pastel = Pastel.new
59
- Logger.error("This is not valid task, #{pastel.bold("install")} property is missing!")
60
- exit(1)
75
+ return has_all_packages_install
76
+ end
77
+
78
+ def do_check
79
+ # If check is not set, always run the task
80
+ if @check == nil
61
81
  return false
62
82
  end
63
83
 
84
+ # If there is no check, default to false, to force task to script every time
85
+ shell = Shell.new(@sensible)
86
+ return shell.run_command(@check, @user)
87
+ end
88
+
89
+ def do_script
90
+ # TODO: Handle the show output property!
91
+ shell = Shell.new(@sensible)
92
+ return shell.run_command(@script, @user)
93
+ end
94
+
95
+ def do_verify
64
96
  return true
65
97
  end
66
98
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sensible
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/sensible.rb CHANGED
@@ -12,21 +12,13 @@ module Sensible
12
12
  attr_reader :opts
13
13
  attr_reader :args
14
14
 
15
- attr_reader :preTasks
16
- attr_reader :packages
17
- attr_reader :requirements
18
- attr_reader :postTasks
19
-
20
- attr_reader :sensible_folder
21
- attr_reader :tasks_folder
15
+ attr_reader :tasks
22
16
 
23
17
 
24
18
  def initialize(sensibleFileName, opts, args)
25
19
  @opts = opts
26
20
  @args = args
27
21
 
28
- @sensible_folder = '.sensible'
29
- @tasks_folder = 'tasks'
30
22
 
31
23
  file_name = opts.file || sensibleFileName
32
24
  unless File.exist?(file_name)
@@ -37,66 +29,85 @@ module Sensible
37
29
  # Load the Sensile file
38
30
  sensible_file_data = YAML.load_file(file_name)
39
31
 
40
- # Parse packages
41
- if sensible_file_data['packages']
42
- @packages = Parse.parse_sensible_packages(sensible_file_data['packages'], self)
43
- end
44
32
 
45
- # Parse packages
46
- if sensible_file_data['requirements']
47
- @requirements = Parse.parse_sensible_requirements(sensible_file_data['requirements'], self)
48
- end
33
+ # Recursively go through all the tasks inside the sensible file
34
+ # Add them to a list, that we will go through later
35
+ @tasks = []
36
+ def process_task(task_line, task_user = nil)
37
+ task = nil
38
+
39
+ # Handle inline tasks inside root sensible file
40
+ if task_line.class == Hash
41
+ task = Task.new(task_line, task_line, task_user, self)
42
+ end
49
43
 
50
- if (sensible_file_data['preTasks'])
51
- @preTasks = sensible_file_data['preTasks']
52
- end
44
+ if task_line.is_a?(String)
45
+ task_yaml = YAML.load_file(task_line + '.yml')
46
+ task = Task.new(task_yaml, task_line, task_user, self)
47
+ end
53
48
 
54
- if (sensible_file_data['postTasks'])
55
- @postTasks = sensible_file_data['postTasks']
49
+ if task.require != nil
50
+ task.require.each do |path|
51
+ process_task(path, task.user)
52
+ end
53
+ end
54
+
55
+ @tasks << task
56
+ end
57
+
58
+ sensible_file_data.each do |key, value|
59
+ case key
60
+ when "require"
61
+ value.each do |path|
62
+ process_task(path)
63
+ end
64
+ when "tasks"
65
+ value.each do |path|
66
+ process_task(path)
67
+ end
68
+ end
56
69
  end
57
70
  end
58
71
 
59
72
 
60
73
  # Run all the checks for packages and requirements
61
74
  def check
62
- Logger.log("Checking for installed packages...")
75
+ # Prewarm sudo, to prevent asking too much
76
+ shell = Shell.new(self)
77
+ shell.run_command('sudo -v', show_output: true)
63
78
 
64
- for pkg in @packages
65
- # Do an environment test
66
- if @opts.env
67
- # If package env is not define, we expect it should always be installed regardless of environment
68
- # If user has defined an environment, skip if the set environment isn't in the package enviroment list
69
- next if pkg.env.any? && !pkg.env.include?(@opts.env)
70
- else
71
- # If env contains anything, when env is not defined in opts, skip it, as this is not the correct env
72
- next if pkg.env.any?
79
+ @tasks.each_with_index do |task, index|
80
+ user = nil
81
+ if task.user
82
+ user = "(User: #{task.user})"
73
83
  end
74
84
 
75
- if pkg.do_check
76
- Logger.success("#{pkg.name} is installed")
85
+ pastel = Pastel.new
86
+ if index > 0
87
+ Logger.log("\n#{pastel.bold(task.name)} #{user}")
77
88
  else
78
- Logger.danger("#{pkg.name} was NOT installed")
89
+ Logger.log("#{pastel.bold(task.name)} #{user}")
79
90
  end
80
- end
81
-
82
- if @requirements != nil
83
- Logger.log("\nChecking if requirements are met...")
84
-
85
- for requirement in @requirements
86
- # Do an environment test
87
- if @opts.env
88
- # If package env is not define, we expect it should always be installed regardless of environment
89
- # If user has defined an environment, skip if the set environment isn't in the package enviroment list
90
- next if requirement.env.any? && !requirement.env.include?(@opts.env)
91
+
92
+ # Do an environment test
93
+ # if @opts.env
94
+ # # If requirement env is not define, we expect it should always be installed regardless of environment
95
+ # # If user has defined an environment, skip if the set environment isn't in the package enviroment list
96
+ # next if requirement.env.any? && !requirement.env.include?(@opts.env)
97
+ # end
98
+ if task.packages.length > 0
99
+ if task.do_packages_check
100
+ Logger.success("Packages installed!")
91
101
  else
92
- # If env contains anything, when env is not defined in opts, skip it, as this is not the correct env
93
- next if pkg.env.any?
102
+ Logger.danger("Packages NOT installed!")
94
103
  end
104
+ end
95
105
 
96
- if requirement.do_check
97
- Logger.success("#{requirement.name}")
106
+ if task.check
107
+ if task.do_check
108
+ Logger.success("Is installed!")
98
109
  else
99
- Logger.danger("#{requirement.name}")
110
+ Logger.danger("Is NOT installed!")
100
111
  end
101
112
  end
102
113
  end
@@ -104,85 +115,66 @@ module Sensible
104
115
 
105
116
  def install
106
117
  # Prewarm sudo, to prevent asking too much
107
- system('sudo -v')
118
+ shell = Shell.new(self)
119
+ shell.run_command('sudo -v', show_output: true)
108
120
 
109
- # Run pre tasks
110
- if @preTasks != nil
111
- Logger.log("Running pre tasks...")
121
+
122
+ @tasks.each_with_index do |task, index|
123
+ user = nil
124
+ if task.user
125
+ user = "(#{task.user})"
126
+ end
112
127
 
113
- for task in @preTasks
114
- task_run(task)
128
+ pastel = Pastel.new
129
+ if index > 0
130
+ Logger.log("\n#{pastel.bold(task.name)} #{user}")
131
+ else
132
+ Logger.log("#{pastel.bold(task.name)} #{user}")
115
133
  end
116
- Logger.log("")
117
- end
118
-
119
- # Install packages
120
- if @packages != nil
121
- Logger.log("Installing packages...")
122
- for pkg in @packages
123
- # Do an environment test
124
- if @opts.env
125
- # If package env is not defined, we expect it should always be installed regardless of environment
126
- # If user has defined an environment, skip if the set environment isn't in the package enviroment list
127
- next if pkg.env.any? && !pkg.env.include?(@opts.env)
128
- else
129
- # If env contains anything, when env is not defined in opts, skip it, as this is not the correct env
130
- next if pkg.env.any?
131
- end
132
134
 
133
- if pkg.do_check
134
- Logger.success("#{pkg.name} is installed")
135
+
136
+ # Do an environment test
137
+ # if @opts.env
138
+ # # If requirement env is not define, we expect it should always be installed regardless of environment
139
+ # # If user has defined an environment, skip if the set environment isn't in the package enviroment list
140
+ # next if requirement.env.any? && !requirement.env.include?(@opts.env)
141
+ # end
142
+ if task.packages.length > 0
143
+ if task.do_packages_check
144
+ Logger.success("Packages installed!")
135
145
  else
136
- Logger.info("Installing: #{pkg.name}\r", use_print: true)
137
- if pkg.do_install
138
- Logger.success("#{pkg.name} was installed")
139
- $stdout.flush
146
+ Logger.info("Installing packages...\r", use_print: true)
147
+ if task.do_packages_install
148
+ Logger.clear_line
149
+ Logger.success("Packages installed!")
140
150
  else
141
- Logger.danger("#{pkg.name} was not installed")
142
- $stdout.flush
151
+ Logger.clear_line
152
+ Logger.danger("Packages NOT installed!")
143
153
  end
154
+
155
+ Logger.flush
144
156
  end
145
- end
146
- Logger.log("")
147
- end
148
-
149
- # Handle requirements
150
- if @requirements != nil
151
- Logger.log("Handling requirements...")
152
- for requirement in @requirements
153
- # Do an environment test
154
- if @opts.env
155
- # If package env is not defined, we expect it should always be installed regardless of environment
156
- # If user has defined an environment, skip if the set environment isn't in the package enviroment list
157
- next if requirement.env.any? && !requirement.env.include?(@opts.env)
158
- else
159
- # If env contains anything, when env is not defined in opts, skip it, as this is not the correct env
160
- next if requirement.env.any?
161
- end
157
+ end
162
158
 
163
- if requirement.do_check
164
- Logger.success("#{requirement.name}")
159
+ if task.check
160
+ if task.do_check
161
+ Logger.success("Is installed!")
165
162
  else
166
- Logger.info("Handling: #{pkg.name}\r", use_print: true)
167
- if requirement.do_install
168
- Logger.success("#{requirement.name}")
169
- $stdout.flush
163
+ if task.script
164
+ Logger.info("Running task...\r", use_print: true)
165
+ if task.do_script
166
+ Logger.clear_line
167
+ Logger.success("Is installed!")
168
+ else
169
+ Logger.clear_line
170
+ Logger.danger("Is NOT installed!")
171
+ end
172
+ Logger.flush
170
173
  else
171
- Logger.danger("#{requirement.name}")
172
- $stdout.flush
174
+ Logger.danger("Is NOT installed!")
173
175
  end
174
176
  end
175
177
  end
176
- Logger.log("")
177
- end
178
-
179
- # Run post tasks
180
- if @postTasks != nil
181
- Logger.log("Running post tasks...")
182
-
183
- for task in @postTasks
184
- task_run(task)
185
- end
186
178
  end
187
179
 
188
180
  end
@@ -0,0 +1,140 @@
1
+ # New sensible specification
2
+
3
+ To structure everything better, and making it efficient for setting up machines or projects, i've come up with a new specification.
4
+
5
+ You are now able to completely able to define your own structure,
6
+
7
+ sensible.yml inside a project for folder:
8
+
9
+ ```yaml
10
+ packages:
11
+ - rpm: [btop]
12
+
13
+ require:
14
+ - .sensible/requirements/postgres17 # Path to the task
15
+ - .sensible/requirements/httpd
16
+
17
+ tasks:
18
+ - .sensible/tasks/test
19
+
20
+ options:
21
+ # Possible future options
22
+ ```
23
+
24
+ The nice thing here is, that the order of where you place the packages, require and tasks property matters. It parses the file from top-to-bottom, so in this example, it install packages first, handle requirements, and run tasks at the end. You could do it in complete reverse order if you wanted.
25
+
26
+ The same thing with the require and tasks property, the list in there, will be handled in that order..
27
+
28
+ When you add an requirement, it's the relative path from where you execute sensible from.
29
+
30
+ ## Folder structure example
31
+
32
+ ```
33
+ .sensible/
34
+ requirements/
35
+ httpd.yml
36
+ postgresql17.yml
37
+ tasks/
38
+ another.yml
39
+ test.yml
40
+ sensible.yml
41
+ ```
42
+
43
+ You could also structure it like this:
44
+
45
+ ```
46
+ services/
47
+ httpd.yml
48
+ postgresql17.yml
49
+ tasks/
50
+ another.yml
51
+ test.yml
52
+ hosts/
53
+ server1.yml
54
+ server2.yml
55
+ ```
56
+
57
+ Then you run the command: `sensible -f check hosts/server1.yml` or `sensible -f install hosts/server1.yml`
58
+
59
+ If you want to have a hosts folder, I recommend using the `-f` option.
60
+
61
+ By default sensible will look for a sensible.yml in the folder you are executing sensible from.
62
+
63
+ ## Tasks
64
+ Everything is basically a task, it manages packages, and the script to check and install the task.
65
+
66
+ ### Properties
67
+
68
+ #### name
69
+ This is name of the task, should be short and simple
70
+
71
+ #### description
72
+ This a longer description to help explain it in more detail what it does.
73
+
74
+ #### require
75
+ A task can require other things. Lets say you have a task that installs a frontend project, that can then require that you have a apache server installed to run.
76
+
77
+ **This property is optional**
78
+
79
+ #### packages
80
+ Here you can definethe packages a task should install. You define which type of package via the property name: `rpm`, `deb` or `aur`
81
+
82
+ Example:
83
+
84
+ ```
85
+ packages:
86
+ rpm: [btop] # RHEL based
87
+ deb: [btop] # Debian based
88
+ aur: [btop] # Arch linux based
89
+ ```
90
+
91
+ **This property is optional**
92
+
93
+ #### check
94
+ This is the script you can use to check if the task was installed succesfully.
95
+
96
+ You will most likely use this, if you are going to use the `script` property.
97
+
98
+ **This property is optional**
99
+
100
+ #### script
101
+ This the install script for the task. Let's say you need to do a git clone, create a user, switch to that user etc.
102
+
103
+ If this script exits with 0, it completed successfully
104
+
105
+ **This property is optional**
106
+
107
+
108
+ ### Full example
109
+
110
+ ```yaml
111
+ # .sensible/task/test.yml
112
+ ---
113
+ name: Task example
114
+ description: This installs an apache httpd server, shellopts gem
115
+ require:
116
+ - .sensible/requirements/httpd # Remember, it's relative from execution path
117
+ packages: # Optional if task does not require packages
118
+ deb: # Debian packages
119
+ aur: # Aur packages (Arch linux)
120
+ rpm: [httpd]
121
+ check: |
122
+ # This is for a more complex check, like if you need to check a service is running, files exist, etc...
123
+ script: |
124
+ # Script is optional, as it's intended for tasks that require more than just installing various packages.
125
+ # Maybe you require things being installed in a certain order
126
+ ```
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+ ### Future ideas
135
+
136
+ #### Allow for ruby files
137
+ I'm planning on allowing .rb-files too, for even more flexibility.
138
+
139
+ #### Include other files
140
+ It might be a nice feature to be able to include other files.
data/sensible.yml CHANGED
@@ -1,23 +1,13 @@
1
- preTasks:
2
- - test
3
-
4
1
  packages:
5
- - name: httpd
6
- - name: htop
7
- - name: btop
8
- env:
9
- - prod
10
- - name: rvm
11
- check: which rvm
2
+ rpm: [btop]
3
+
4
+
5
+ tasks:
6
+ - name: Check whoami
7
+ user: sample
8
+ check: whoami
9
+ script: whoami
12
10
 
13
- requirements:
14
- - name: Test requirement
15
- check: which node
16
- install: echo "install"
17
-
18
- - name: Test requirement that fails
19
- check: exit 1
20
- install: exit 1
21
-
22
- postTasks:
23
- - another
11
+ - .sensible/apps/project-frontend
12
+ - .sensible/apps/project-frontend-2
13
+ - .sensible/tasks/test
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sensible-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Jensen
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-13 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: shellopts
@@ -15,14 +15,20 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '0'
18
+ version: 2.6.1
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '3'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '0'
28
+ version: 2.6.1
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '3'
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: tty-which
28
34
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +99,34 @@ dependencies:
93
99
  - - ">="
94
100
  - !ruby/object:Gem::Version
95
101
  version: '0'
102
+ - !ruby/object:Gem::Dependency
103
+ name: net-ssh
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ type: :runtime
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ - !ruby/object:Gem::Dependency
117
+ name: logger
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: 1.6.0
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: 1.6.0
96
130
  description: A small tool to manage projects, making shell scripts easier to manage,
97
131
  and faster to setup projects!
98
132
  email:
@@ -102,9 +136,13 @@ executables:
102
136
  extensions: []
103
137
  extra_rdoc_files: []
104
138
  files:
139
+ - ".idea/.gitignore"
140
+ - ".idea/material_theme_project_new.xml"
141
+ - ".idea/misc.xml"
142
+ - ".idea/modules.xml"
143
+ - ".idea/sensible-ruby.iml"
144
+ - ".idea/vcs.xml"
105
145
  - ".rspec"
106
- - ".sensible/tasks/another.yml"
107
- - ".sensible/tasks/test.yml"
108
146
  - README.md
109
147
  - Rakefile
110
148
  - exe/sensible
@@ -113,8 +151,10 @@ files:
113
151
  - lib/sensible/package.rb
114
152
  - lib/sensible/parse.rb
115
153
  - lib/sensible/requirement.rb
154
+ - lib/sensible/shell.rb
116
155
  - lib/sensible/task.rb
117
156
  - lib/sensible/version.rb
157
+ - new-sensible-spec.md
118
158
  - sensible.yml
119
159
  - sig/sensible.rbs
120
160
  homepage: https://github.com/dasmikko/sensible-ruby
@@ -135,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
175
  - !ruby/object:Gem::Version
136
176
  version: '0'
137
177
  requirements: []
138
- rubygems_version: 3.6.5
178
+ rubygems_version: 3.6.9
139
179
  specification_version: 4
140
180
  summary: A small tool to manage projects.
141
181
  test_files: []
@@ -1,7 +0,0 @@
1
- ---
2
- name: Another test task
3
- description: This is a sample task, and this is its description
4
- install: |
5
- echo "This is a multi line script"
6
- echo "I should use a temp file to run"
7
- notify-send "This is the post task that ran!"
@@ -1,4 +0,0 @@
1
- ---
2
- name: This is a sample task in a seperate file
3
- description: This is a sample task, and this is its description
4
- install: notify-send "This is the pre task that ran!"