nutella_framework 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|