nutella_framework 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.travis.yml +5 -0
- data/Gemfile +17 -0
- data/LICENSE +20 -0
- data/README.md +11 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/bin/nutella +5 -0
- data/lib/cli/nutella_cli.rb +44 -0
- data/lib/config/config.rb +72 -0
- data/lib/config/project.rb +53 -0
- data/lib/config/runlist.rb +91 -0
- data/lib/core/command.rb +12 -0
- data/lib/core/commands/broker.rb +58 -0
- data/lib/core/commands/checkup.rb +91 -0
- data/lib/core/commands/help.rb +22 -0
- data/lib/core/commands/install.rb +153 -0
- data/lib/core/commands/new.rb +62 -0
- data/lib/core/commands/runs.rb +42 -0
- data/lib/core/commands/start.rb +101 -0
- data/lib/core/commands/stop.rb +62 -0
- data/lib/core/nutella_core.rb +32 -0
- data/lib/core/tmux.rb +33 -0
- data/lib/logging/nutella_logger-remote.rb +31 -0
- data/lib/logging/nutella_logger.rb +42 -0
- data/lib/logging/nutella_logging.rb +35 -0
- data/lib/nutella_framework.rb +17 -0
- data/test/config/test_config.rb +47 -0
- data/test/config/test_project.rb +32 -0
- data/test/config/test_runlist.rb +46 -0
- data/test/helper.rb +36 -0
- data/test/logging/test_logging.rb +16 -0
- data/test/test_nutella_framework.rb +30 -0
- metadata +239 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'core/command'
|
2
|
+
require 'semantic'
|
3
|
+
|
4
|
+
module Nutella
|
5
|
+
class Checkup < Command
|
6
|
+
@description = "Checks that all the dependencies are installed and prepares nutella to run"
|
7
|
+
|
8
|
+
def run(args=nil)
|
9
|
+
# First check that we have all the tools we need to run nutella
|
10
|
+
if !allDependenciesInstalled?
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
# Check if we have a broker and install one if not
|
15
|
+
if !File.directory? Nutella.config["broker_dir"]
|
16
|
+
console.warn "You don't seem to have a local broker installed so we are going to go ahead and install one for you. This might take some time..."
|
17
|
+
if !installBroker
|
18
|
+
console.error "Whoops...something went wrong while installing the broker. "
|
19
|
+
return
|
20
|
+
end
|
21
|
+
else
|
22
|
+
console.info "You have a local broker installed. Yay!"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set config and output message
|
26
|
+
Nutella.config["ready"] = true
|
27
|
+
console.success("All systems go! You are ready to use nutella!")
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
|
34
|
+
def installBroker
|
35
|
+
# Clone, cd and npm install
|
36
|
+
out1 = system "git clone git://github.com/mcollina/mosca.git #{Nutella.config["broker_dir"]} > /dev/null 2>&1"
|
37
|
+
Dir.chdir(Nutella.config["broker_dir"])
|
38
|
+
out2 = system "npm install > /dev/null 2>&1"
|
39
|
+
|
40
|
+
# Add startup script
|
41
|
+
File.open("startup", 'w') { |file| file.write("#!/bin/sh\n\nBASEDIR=$(dirname $0)\n$BASEDIR/bin/mosca --http-port 1884 &\necho $! > $BASEDIR/bin/.pid\n") }
|
42
|
+
File.chmod(0755, "startup")
|
43
|
+
|
44
|
+
# Add configuration
|
45
|
+
Nutella.config["broker"] = "localhost"
|
46
|
+
return out1 && out2
|
47
|
+
end
|
48
|
+
|
49
|
+
def allDependenciesInstalled?
|
50
|
+
# Node version lambda
|
51
|
+
node_semver = lambda do
|
52
|
+
out = `node --version`
|
53
|
+
out[0] = ''
|
54
|
+
Semantic::Version.new out
|
55
|
+
end
|
56
|
+
# Git version lambda
|
57
|
+
git_semver = lambda do
|
58
|
+
out = `git --version`
|
59
|
+
out.slice!(0,12)
|
60
|
+
Semantic::Version.new out[0..4]
|
61
|
+
end
|
62
|
+
# Check versions
|
63
|
+
if checkVersion?("node", "0.10.0", node_semver) && checkVersion?("git", "1.8.0", git_semver)
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def checkVersion?(dep, req_version, lambda)
|
71
|
+
begin
|
72
|
+
actual_version = lambda.call
|
73
|
+
rescue
|
74
|
+
console.warn "Doesn't look like #{dep} is installed in your system. " +
|
75
|
+
"Unfotunately nutella can't do much unless all the dependencies are installed :("
|
76
|
+
return
|
77
|
+
end
|
78
|
+
required_version = Semantic::Version.new req_version
|
79
|
+
if actual_version < required_version
|
80
|
+
console.warn "Your version of #{dep} is a little old (#{actual_version}). Nutella requires #{required_version}. Please upgrade!"
|
81
|
+
return
|
82
|
+
else
|
83
|
+
console.info "Your #{dep} version is #{actual_version}. Yay!"
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'core/command'
|
2
|
+
|
3
|
+
module Nutella
|
4
|
+
|
5
|
+
class Help < Command
|
6
|
+
@description = "Displays what every command does and how to use it"
|
7
|
+
|
8
|
+
def run(args=nil)
|
9
|
+
message=""
|
10
|
+
Dir[File.dirname(__FILE__)+"/*.rb"].each do |file|
|
11
|
+
message += "#{File.basename(file, File.extname(file))}\t\t"
|
12
|
+
message += Object::const_get("Nutella::#{File.basename(file, File.extname(file)).capitalize}").description
|
13
|
+
message += "\n"
|
14
|
+
end
|
15
|
+
console.info message, 200
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'core/command'
|
2
|
+
require 'json'
|
3
|
+
require 'git'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module Nutella
|
7
|
+
|
8
|
+
class Install < Command
|
9
|
+
@description = "Copies an arbitrary template (from central DB, directory or URL) into the current project"
|
10
|
+
|
11
|
+
def run(args=nil)
|
12
|
+
# Is current directory a nutella prj?
|
13
|
+
if !Nutella.currentProject.exist?
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
# Check args
|
18
|
+
if args.empty?
|
19
|
+
console.warn "You need to specify a template name, directory or URL"
|
20
|
+
return
|
21
|
+
end
|
22
|
+
@template = args[0]
|
23
|
+
if args.length==2
|
24
|
+
@destinationFolder = args[1]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Extract project directory
|
28
|
+
@prj_dir = Nutella.currentProject.dir
|
29
|
+
|
30
|
+
# What kind of template are we handling?
|
31
|
+
if isTemplateALocalDir?
|
32
|
+
addLocalTemplate
|
33
|
+
elsif isTemplateAGitRepo?
|
34
|
+
addRemoteTemplate
|
35
|
+
elsif isTemplateInCentralDB?
|
36
|
+
addCentralTemplate
|
37
|
+
else
|
38
|
+
console.warn "The specified template is not a valid nutella template"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
|
47
|
+
def isTemplateALocalDir?
|
48
|
+
# Does the specified directory exist?
|
49
|
+
if !File.directory?(@template)
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
return validateTemplate @template
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def isTemplateAGitRepo?
|
57
|
+
begin
|
58
|
+
dest_dir = @template[@template.rindex("/")+1 .. @template.length-5]
|
59
|
+
cloneTemplateFromRemoteTo(dest_dir)
|
60
|
+
return validateTemplate("#{Nutella.config["tmp_dir"]}/#{dest_dir}")
|
61
|
+
rescue
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def isTemplateInCentralDB?
|
69
|
+
uri = URI.parse("https://raw.githubusercontent.com/ltg-uic/nutella/templates-database/" + @template + ".json")
|
70
|
+
begin
|
71
|
+
nutella_json = JSON.parse(Net::HTTP.get(uri))
|
72
|
+
if nutella_json["name"]==@template
|
73
|
+
@template = nutella_json["repo"]
|
74
|
+
return isTemplateAGitRepo?
|
75
|
+
end
|
76
|
+
rescue
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def addLocalTemplate
|
84
|
+
templateNutellaFileJson = JSON.parse(IO.read("#{@template}/nutella.json"))
|
85
|
+
|
86
|
+
# If destination is not specified, set it to the template name
|
87
|
+
if @destinationFolder==nil
|
88
|
+
@destinationFolder = templateNutellaFileJson["name"]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Am I trying to copy onto a template that already exists?
|
92
|
+
if templateNutellaFileJson["type"]=="bot"
|
93
|
+
# Look into bots folder
|
94
|
+
dest_dir = "#{@prj_dir}/bots/#{@destinationFolder}"
|
95
|
+
else
|
96
|
+
# Look into interfaces folder
|
97
|
+
dest_dir = "#{@prj_dir}/interfaces/#{@destinationFolder}"
|
98
|
+
end
|
99
|
+
if File.directory?(dest_dir)
|
100
|
+
console.error("Folder #{dest_dir} aready exists! Can't add template #{@template}")
|
101
|
+
return
|
102
|
+
end
|
103
|
+
FileUtils.copy_entry @template, dest_dir
|
104
|
+
console.success("Installed template: #{@template} as #{dest_dir}")
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def addRemoteTemplate
|
109
|
+
templ_name = @template[@template.rindex("/")+1 .. @template.length-5]
|
110
|
+
addLocalTemplate "#{Nutella.config["tmp_dir"]}/#{templ_name}"
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def addCentralTemplate
|
115
|
+
return addRemoteTemplate
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def cloneTemplateFromRemoteTo(dest_dir)
|
120
|
+
cleanTmpDir
|
121
|
+
if !Dir.exists?(Nutella.config["tmp_dir"])
|
122
|
+
Dir.mkdir(Nutella.config["tmp_dir"])
|
123
|
+
end
|
124
|
+
Git.clone(@template, dest_dir, :path => Nutella.config["tmp_dir"])
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
def validateTemplate(dir)
|
129
|
+
# Parse the template's nutella.json file
|
130
|
+
begin
|
131
|
+
templateNutellaFileJson = JSON.parse(IO.read("#{dir}/nutella.json"))
|
132
|
+
rescue
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
# If template is a bot, perform additional checks
|
136
|
+
if templateNutellaFileJson["type"]=="bot"
|
137
|
+
# Is there a andatory 'startup' script and is it executable
|
138
|
+
if !File.executable?("#{dir}/startup")
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
true
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def cleanTmpDir
|
147
|
+
FileUtils.rm_rf Nutella.config["tmp_dir"]
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'core/command'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Nutella
|
5
|
+
class New < Command
|
6
|
+
@description = "Creates a new project"
|
7
|
+
|
8
|
+
def run(args=nil)
|
9
|
+
@prj_dir = args[0]
|
10
|
+
|
11
|
+
# If no other arguments, show help and quit here
|
12
|
+
if args.empty?
|
13
|
+
console.warn "You need to specify a name for your new project. Can't create project."
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
# Does a project/directory with the same name exist already?
|
18
|
+
if File.directory?(@prj_dir)
|
19
|
+
if File.exist?("#{@prj_dir}/conf/project.json")
|
20
|
+
console.warn "A project named #{@prj_dir} already exists. Can't create project."
|
21
|
+
return
|
22
|
+
else
|
23
|
+
console.warn "A directory named #{@prj_dir} already exists. Can't create project."
|
24
|
+
return
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generate project structure
|
29
|
+
@cur_dir = Dir.pwd # Store current directory
|
30
|
+
createDirStructure # Create project directory structure
|
31
|
+
Dir.chdir @prj_dir # CD into the project
|
32
|
+
# Display a nice success message and return
|
33
|
+
console.success "Your new project #{@prj_dir} is ready!"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
|
40
|
+
def createDirStructure
|
41
|
+
FileUtils.mkdir_p("#{@prj_dir}/bots") # bots dir
|
42
|
+
FileUtils.mkdir_p("#{@prj_dir}/interfaces") # interfaces dir
|
43
|
+
FileUtils.mkdir_p("#{@prj_dir}/conf") # conf dir
|
44
|
+
# create base configuration file
|
45
|
+
config_file_hash = {
|
46
|
+
"nutella_version" => File.open(NUTELLA_HOME+"VERSION", "rb").read,
|
47
|
+
"name" => @prj_dir,
|
48
|
+
"version" => "0.1.0-SNAPSHOT"
|
49
|
+
}
|
50
|
+
File.open("#{@prj_dir}/conf/project.json","w") do |f|
|
51
|
+
f.write(JSON.pretty_generate(config_file_hash))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def removeDirStructure
|
56
|
+
Dir.chdir @cur_dir
|
57
|
+
console.info "Removing project #{@prj_dir}"
|
58
|
+
FileUtils.rm_rf(@prj_dir)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'core/command'
|
2
|
+
|
3
|
+
|
4
|
+
module Nutella
|
5
|
+
class Runs < Command
|
6
|
+
@description = "Displays list of all the runs, you can filter by passing a project id"
|
7
|
+
|
8
|
+
def run(args=nil)
|
9
|
+
# If invoked with "--all" it will show all the runs under this instance of nutella
|
10
|
+
if args[0]=="--all"
|
11
|
+
if Nutella.runlist.empty?
|
12
|
+
console.info "You are not running any projects"
|
13
|
+
else
|
14
|
+
console.info "Currently running:"
|
15
|
+
Nutella.runlist.to_a.each { |run| console.info " #{run}" }
|
16
|
+
end
|
17
|
+
else # Just show the runs associated with this project
|
18
|
+
# Is current directory a nutella prj?
|
19
|
+
if !Nutella.currentProject.exist?
|
20
|
+
return
|
21
|
+
end
|
22
|
+
project_name = Nutella.currentProject.config["name"]
|
23
|
+
runs = Nutella.runlist.to_a project_name
|
24
|
+
if runs.empty?
|
25
|
+
console.info "Currently running #{runs.length} instances of project #{project_name}"
|
26
|
+
return
|
27
|
+
end
|
28
|
+
console.info "Currently running #{runs.length} instances of project #{project_name}:"
|
29
|
+
runs.to_a.each { |run|
|
30
|
+
run.slice! "#{project_name}_"
|
31
|
+
if run.empty?
|
32
|
+
console.info "progetto (default)"
|
33
|
+
else
|
34
|
+
console.info " #{run}"
|
35
|
+
end
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'core/command'
|
2
|
+
require 'core/tmux'
|
3
|
+
|
4
|
+
module Nutella
|
5
|
+
class Start < Command
|
6
|
+
@description = "Starts all or some of the bots in the current project"
|
7
|
+
|
8
|
+
def run(args=nil)
|
9
|
+
# Is current directory a nutella prj?
|
10
|
+
if !Nutella.currentProject.exist?
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
# Extract runid
|
15
|
+
runid = args[0].to_s.empty? ? Nutella.currentProject.config["name"] : Nutella.currentProject.config["name"] + "_" + args[0]
|
16
|
+
|
17
|
+
# Add to the list of runs and check the runId is unique
|
18
|
+
if !Nutella.runlist.add?(runid)
|
19
|
+
console.error "Impossible to start project: an instance of this project with the same name is already running!"
|
20
|
+
console.error "You might want to kill it with 'nutella stop "+ runid + "'"
|
21
|
+
return;
|
22
|
+
end
|
23
|
+
|
24
|
+
# Extract project directory
|
25
|
+
@prj_dir = Nutella.currentProject.dir
|
26
|
+
|
27
|
+
# If running on internal broker, start it
|
28
|
+
if Nutella.config["broker"] == "localhost" # Are we using the internal broker
|
29
|
+
startBroker
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create .botsconfig file
|
33
|
+
deleteBotsConfig
|
34
|
+
createBotsConfig
|
35
|
+
|
36
|
+
|
37
|
+
# Start all the bots
|
38
|
+
tmux = Tmux.new(runid)
|
39
|
+
Dir.entries("#{@prj_dir}/bots").select {|entry| File.directory?(File.join("#{@prj_dir}/bots",entry)) and !(entry =='.' || entry == '..') }.each do |bot|
|
40
|
+
if !File.exist?("#{@prj_dir}/bots/#{bot}/startup")
|
41
|
+
console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script."
|
42
|
+
next
|
43
|
+
end
|
44
|
+
tmux.newWindow(bot)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Output success message
|
48
|
+
if runid == Nutella.currentProject.config["name"]
|
49
|
+
console.success "Project " + Nutella.currentProject.config["name"] + " started"
|
50
|
+
else
|
51
|
+
console.success "Project " + Nutella.currentProject.config["name"] + ", run " + args[0] + " started"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
|
59
|
+
def startBroker
|
60
|
+
pidFile = "#{Nutella.config["broker_dir"]}/bin/.pid"
|
61
|
+
if File.exist?(pidFile) # Does the broker pid file exist?
|
62
|
+
pidF = File.open(pidFile, "rb")
|
63
|
+
pid = pidF.read.to_i
|
64
|
+
pidF.close()
|
65
|
+
begin
|
66
|
+
Process.getpgid(pid) #PID is still alive
|
67
|
+
# broker is already started and I do nothing
|
68
|
+
rescue
|
69
|
+
# broker is dead but we have a stale pid file
|
70
|
+
File.delete(pidFile)
|
71
|
+
startAndCreatePid()
|
72
|
+
end
|
73
|
+
else
|
74
|
+
# Broker is not running and there is no file
|
75
|
+
startAndCreatePid()
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def startAndCreatePid()
|
80
|
+
pid = fork
|
81
|
+
exec("#{Nutella.config["broker_dir"]}/startup") if pid.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
def createBotsConfig
|
85
|
+
botsconfig = Nutella.config.to_h
|
86
|
+
botsconfig.delete(:runs)
|
87
|
+
botsconfig[:prj_name] = Nutella.currentProject.config["name"]
|
88
|
+
File.open("#{@prj_dir}/.botsconfig.json", "w") do |f|
|
89
|
+
f.write(JSON.pretty_generate(botsconfig))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def deleteBotsConfig
|
94
|
+
File.delete("#{@prj_dir}/.botsconfig.json") if File.exist?("#{@prj_dir}/.botsconfig.json")
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|