disloku 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/bin/disloku +4 -0
- data/lib/disloku/BaseTask.rb +39 -0
- data/lib/disloku/ChangeSet.rb +14 -0
- data/lib/disloku/ChangeSetProvider.rb +13 -0
- data/lib/disloku/Commit.rb +7 -0
- data/lib/disloku/Config.rb +52 -0
- data/lib/disloku/Connection.rb +31 -0
- data/lib/disloku/ConnectionStore.rb +16 -0
- data/lib/disloku/Disloku.rb +103 -0
- data/lib/disloku/DislokuError.rb +5 -0
- data/lib/disloku/FileChange.rb +17 -0
- data/lib/disloku/Log.rb +35 -0
- data/lib/disloku/Mapping.rb +81 -0
- data/lib/disloku/MappingStore.rb +16 -0
- data/lib/disloku/NamedConfigStore.rb +39 -0
- data/lib/disloku/Options.rb +38 -0
- data/lib/disloku/Repository.rb +29 -0
- data/lib/disloku/SessionManager.rb +38 -0
- data/lib/disloku/SysCmd.rb +15 -0
- data/lib/disloku/SysCmdResult.rb +12 -0
- data/lib/disloku/Target.rb +35 -0
- data/lib/disloku/git/ChangeSetProvider.rb +48 -0
- data/lib/disloku/git/Repository.rb +31 -0
- data/lib/disloku/svn/ChangeSetProvider.rb +41 -0
- data/lib/disloku/svn/Repository.rb +22 -0
- data/lib/disloku/tasks/FolderTask.rb +79 -0
- data/lib/disloku/tasks/NetSftpTask.rb +95 -0
- data/lib/disloku/tasks/PsFtpTask.rb +58 -0
- data/lib/disloku/util/File.rb +47 -0
- data/lib/disloku/util/Hash.rb +12 -0
- data/lib/disloku.rb +32 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 114b22aa9d4750cc34ec61df2eaa35db116d2019
|
4
|
+
data.tar.gz: e3ca011d1d3b297c073dec2a226fce5d290c6ef4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 405cdd36e1552d1504d660cb604ce12d3bb3c5e05507b593c710a4bd761b24f031b12d98f02cb3e81e06d5d85353457212deed90ad235ad5bd21acfd5cd7ba1a
|
7
|
+
data.tar.gz: a098da49f71bbec30fdc13b55572e6f7d19a640cf52c2d9408d6861b7fd5518399796de84dbb3707b35a719d7389f88982674ae459283f702fc1064c4de11a1e
|
data/bin/disloku
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
module Disloku
|
3
|
+
class BaseTask
|
4
|
+
attr_accessor :changesets
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
@result = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def getInputParam(input, name, klass)
|
11
|
+
if (!input.has_key?(name))
|
12
|
+
raise ArgumentError.new("Missing input argument '#{name}' of type '#{klass}'")
|
13
|
+
end
|
14
|
+
if (!input[name].kind_of?(klass))
|
15
|
+
raise ArgumentError.new("Input argument '#{name}' is not of type '#{klass}'")
|
16
|
+
end
|
17
|
+
|
18
|
+
return input[name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute()
|
22
|
+
Log.instance.info("running task '#{self.class}'")
|
23
|
+
beforeExecute()
|
24
|
+
executeTask()
|
25
|
+
afterExecute()
|
26
|
+
return @result
|
27
|
+
end
|
28
|
+
|
29
|
+
def beforeExecute()
|
30
|
+
end
|
31
|
+
|
32
|
+
def executeTask()
|
33
|
+
raise NotImplementedError.new()
|
34
|
+
end
|
35
|
+
|
36
|
+
def afterExecute()
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require('YAML')
|
2
|
+
require_relative('util/Hash')
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
class Config
|
6
|
+
attr_accessor :yaml
|
7
|
+
|
8
|
+
def initialize(config, isFile = true)
|
9
|
+
if (isFile)
|
10
|
+
@yaml = YAML.load_file(config)
|
11
|
+
else
|
12
|
+
@yaml = config
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def merge(base)
|
17
|
+
@yaml = base.yaml.recursive_merge(@yaml)
|
18
|
+
end
|
19
|
+
|
20
|
+
def has?(key)
|
21
|
+
return self[key] != nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(keys)
|
25
|
+
if (keys.empty?())
|
26
|
+
return self
|
27
|
+
end
|
28
|
+
current = keys.shift()
|
29
|
+
return @yaml.has_key?(current) ? Config.new(@yaml[current], false).get(keys) : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](key)
|
33
|
+
return self.get(key.split('.'))
|
34
|
+
end
|
35
|
+
|
36
|
+
def value()
|
37
|
+
if (@yaml.kind_of?(Array))
|
38
|
+
return @yaml.map() { |item| Config.new(item, false) }
|
39
|
+
end
|
40
|
+
return @yaml
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s()
|
44
|
+
return value().to_s()
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_yaml()
|
48
|
+
return @yaml.to_yaml()
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require('net/sftp')
|
2
|
+
require('digest/sha1')
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
class Connection
|
6
|
+
attr_accessor :hash, :host, :user, :options
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@host = config["host"].value()
|
10
|
+
@user = config["user"].value() if !config["user"].nil?
|
11
|
+
@options = {}
|
12
|
+
addOption(config, :password)
|
13
|
+
addOption(config, :port)
|
14
|
+
addOption(config, :keys, true)
|
15
|
+
|
16
|
+
@hash = Digest::SHA1.hexdigest([@host, @user, @options].join())
|
17
|
+
end
|
18
|
+
|
19
|
+
def addOption(config, key, unwrap = false)
|
20
|
+
value = config[key.to_s()]
|
21
|
+
if (!value.nil?)
|
22
|
+
if (unwrap)
|
23
|
+
@options[key] = value.value().map() { |e| e.value() }
|
24
|
+
else
|
25
|
+
@options[key] = value.value()
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative('NamedConfigStore')
|
2
|
+
require_relative('Connection')
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
class ConnectionStore < NamedConfigStore
|
6
|
+
|
7
|
+
def initialize(config = nil)
|
8
|
+
super(config)
|
9
|
+
end
|
10
|
+
|
11
|
+
def transformConfig(configObject)
|
12
|
+
return Connection.new(configObject)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative('DislokuError')
|
2
|
+
require_relative('Config')
|
3
|
+
require_relative('Options')
|
4
|
+
require_relative('MappingStore')
|
5
|
+
require_relative('ConnectionStore')
|
6
|
+
require_relative('Target')
|
7
|
+
require_relative('git/Repository')
|
8
|
+
require_relative('tasks/FolderTask')
|
9
|
+
require_relative('tasks/NetSftpTask')
|
10
|
+
|
11
|
+
module Disloku
|
12
|
+
class Disloku
|
13
|
+
attr_accessor :repository, :config
|
14
|
+
|
15
|
+
def initialize(cliOptions)
|
16
|
+
@repository = Git::Repository.new(cliOptions[:dir])
|
17
|
+
@config = loadConfiguration()
|
18
|
+
@options = Options.new(@config["options"], cliOptions)
|
19
|
+
@mappingStore = MappingStore.new(@config["mappings"])
|
20
|
+
@connectionStore = ConnectionStore.new(@config["connections"])
|
21
|
+
end
|
22
|
+
|
23
|
+
def loadConfiguration()
|
24
|
+
repoConfig = File.join(repository.root, 'disloku.config')
|
25
|
+
if (!File.exists?(repoConfig))
|
26
|
+
raise DislokuError.new("There is no disloku.config file in #{repository.root}")
|
27
|
+
end
|
28
|
+
|
29
|
+
config = Config.new(repoConfig)
|
30
|
+
|
31
|
+
userHome = File.expand_path("~")
|
32
|
+
userConfig = File.join(userHome, ".disloku.config")
|
33
|
+
if (File.exists?(userConfig))
|
34
|
+
base = Config.new(userConfig)
|
35
|
+
config.merge(base)
|
36
|
+
end
|
37
|
+
|
38
|
+
return config
|
39
|
+
end
|
40
|
+
|
41
|
+
def buildPackage(from, to)
|
42
|
+
changeset = @repository.getChangeSet(from, to)
|
43
|
+
|
44
|
+
folderInput = {
|
45
|
+
:options => @options,
|
46
|
+
:allowOverride => false,
|
47
|
+
:changesets => [changeset],
|
48
|
+
:target => nil,
|
49
|
+
}
|
50
|
+
|
51
|
+
resolveTargets([@options.target]).each() do |t|
|
52
|
+
folderInput[:target] = t
|
53
|
+
|
54
|
+
result = Tasks::FolderTask.new(folderInput).execute()
|
55
|
+
p(result)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def deployPackage(from, to)
|
60
|
+
changeset = @repository.getChangeSet(from, to)
|
61
|
+
|
62
|
+
folderInput = {
|
63
|
+
:options => @options,
|
64
|
+
:allowOverride => false,
|
65
|
+
:changesets => [changeset],
|
66
|
+
:target => nil,
|
67
|
+
}
|
68
|
+
|
69
|
+
resolveTargets([@options.target]).each() do |t|
|
70
|
+
folderInput[:target] = t
|
71
|
+
|
72
|
+
result = Tasks::FolderTask.new(folderInput).execute()
|
73
|
+
|
74
|
+
sftpInput = result.merge({
|
75
|
+
:repository => @repository,
|
76
|
+
:options => @options,
|
77
|
+
:target => t,
|
78
|
+
})
|
79
|
+
|
80
|
+
result = Tasks::NetSftpTask.new(sftpInput).execute()
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def resolveTargets(targets)
|
85
|
+
actualTargets = []
|
86
|
+
|
87
|
+
while (targets.count > 0)
|
88
|
+
current = targets.shift()
|
89
|
+
targetConfig = @config["targets"][current]
|
90
|
+
if (targetConfig != nil)
|
91
|
+
if (targetConfig["targets"] != nil)
|
92
|
+
targetConfig["targets"].value().each() { |x| targets.push(x.value()) }
|
93
|
+
next
|
94
|
+
end
|
95
|
+
|
96
|
+
actualTargets.push(Target.new(current, targetConfig, @mappingStore, @connectionStore))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
return actualTargets
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative('util/File')
|
2
|
+
|
3
|
+
module Disloku
|
4
|
+
class FileChange
|
5
|
+
attr_accessor :repository, :changeType
|
6
|
+
|
7
|
+
def initialize(repository, fullPath, changeType)
|
8
|
+
@repository = repository
|
9
|
+
@fullPath = fullPath
|
10
|
+
@changeType = changeType
|
11
|
+
end
|
12
|
+
|
13
|
+
def getFile(target)
|
14
|
+
return Util::File.new(@fullPath, @repository.root, target, self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/disloku/Log.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require('Singleton')
|
2
|
+
require('Logger')
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
class Log
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
@logger = Logger.new(STDOUT)
|
10
|
+
@logger.formatter = proc { |severity, datetime, progname, msg|
|
11
|
+
"#{msg}\n"
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def debug(message)
|
16
|
+
@logger.debug(message)
|
17
|
+
end
|
18
|
+
|
19
|
+
def info(message)
|
20
|
+
@logger.debug(message)
|
21
|
+
end
|
22
|
+
|
23
|
+
def warn(message)
|
24
|
+
@logger.debug(message)
|
25
|
+
end
|
26
|
+
|
27
|
+
def error(message)
|
28
|
+
@logger.debug(message)
|
29
|
+
end
|
30
|
+
|
31
|
+
def fatal(message)
|
32
|
+
@logger.debug(message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative('util/Hash')
|
2
|
+
require_relative('util/File')
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
class Mapping
|
6
|
+
|
7
|
+
def initialize(config, mappingStore = nil, allowDefault = true)
|
8
|
+
@mapping = {}
|
9
|
+
|
10
|
+
mappingConfig = config["mapping"]
|
11
|
+
if (!mappingConfig.nil?)
|
12
|
+
mappingConfig.value().each() do |m|
|
13
|
+
node = @mapping
|
14
|
+
src = m["src"].value()
|
15
|
+
|
16
|
+
if (src.kind_of?(Symbol))
|
17
|
+
segments = [src]
|
18
|
+
else
|
19
|
+
segments = src.split(/#{Util::SPLIT_EXP}/)
|
20
|
+
|
21
|
+
segments[0..-2].each() do |segment|
|
22
|
+
if (!node.has_key?(segment))
|
23
|
+
node[segment] = {}
|
24
|
+
end
|
25
|
+
node = node[segment]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if (!m["block"].nil? && m["block"].value())
|
30
|
+
node[segments[-1]] = :block
|
31
|
+
else
|
32
|
+
dst = m["dst"].value()
|
33
|
+
if (dst.kind_of?(Symbol))
|
34
|
+
node[segments[-1]] = dst
|
35
|
+
else
|
36
|
+
node[segments[-1]] = m["dst"].value().split(/#{Util::SPLIT_EXP}/)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
elsif (allowDefault)
|
41
|
+
@mapping[:any] = :keep
|
42
|
+
end
|
43
|
+
|
44
|
+
baseMapping = config["baseMapping"].nil? ? nil : config["baseMapping"].value()
|
45
|
+
|
46
|
+
if (!baseMapping.nil?)
|
47
|
+
if (mappingStore.nil?)
|
48
|
+
raise ArgumentError.new("mapping has a base but no mapping manager was passed")
|
49
|
+
else
|
50
|
+
@mapping = mappingStore.get(baseMapping).getTree().recursive_merge(@mapping)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def getTree()
|
56
|
+
return @mapping
|
57
|
+
end
|
58
|
+
|
59
|
+
def mapPath(pathSegments)
|
60
|
+
node = @mapping
|
61
|
+
for i in 0..pathSegments.count
|
62
|
+
if (node.has_key?(pathSegments[i]))
|
63
|
+
node = node[pathSegments[i]]
|
64
|
+
elsif (node.has_key?(:any))
|
65
|
+
node = node[:any]
|
66
|
+
else
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
|
70
|
+
if (node == :block)
|
71
|
+
return nil
|
72
|
+
elsif (node == :keep)
|
73
|
+
return pathSegments
|
74
|
+
elsif (node.kind_of?(Array))
|
75
|
+
return Array.new(node).concat(pathSegments[(i + 1)..-1])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative('NamedConfigStore')
|
2
|
+
require_relative('Mapping')
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
class MappingStore < NamedConfigStore
|
6
|
+
|
7
|
+
def initialize(config = nil)
|
8
|
+
super(config)
|
9
|
+
end
|
10
|
+
|
11
|
+
def transformConfig(configObject)
|
12
|
+
return Mapping.new(configObject, self, false)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative('DislokuError')
|
2
|
+
require_relative('Connection')
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
class NamedConfigStore
|
6
|
+
|
7
|
+
def initialize(config = nil)
|
8
|
+
@store = {}
|
9
|
+
if (!config.nil?)
|
10
|
+
load(config)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(name)
|
15
|
+
if (@store.has_key?(name))
|
16
|
+
return @store[name]
|
17
|
+
else
|
18
|
+
raise DislokuError.new("There is no stored object with the name '#{name}' in this store")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add(name, configObject)
|
23
|
+
@store[name] = transformConfig(configObject)
|
24
|
+
end
|
25
|
+
|
26
|
+
def load(config)
|
27
|
+
if (!config.nil?)
|
28
|
+
config.value().each_key() do |key|
|
29
|
+
add(key, config[key])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def transformConfig(configObject)
|
35
|
+
return configObject
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative('Config')
|
2
|
+
require('fileutils')
|
3
|
+
require('tmpdir')
|
4
|
+
|
5
|
+
module Disloku
|
6
|
+
class Options
|
7
|
+
|
8
|
+
def initialize(config, cliOptions)
|
9
|
+
@options = {
|
10
|
+
:ignoreDeleteErrors => false,
|
11
|
+
:createDeletesFile => false,
|
12
|
+
:target => "default",
|
13
|
+
:packageDir => :temp,
|
14
|
+
}
|
15
|
+
|
16
|
+
@options.each_key() do |key|
|
17
|
+
if (cliOptions.has_key?(key.to_s()))
|
18
|
+
@options[key] = cliOptions[key.to_s()]
|
19
|
+
elsif (config[key.to_s()] != nil)
|
20
|
+
@options[key] = config[key.to_s()].value()
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if (@options[:packageDir] == :temp)
|
25
|
+
puts("creating tempdir")
|
26
|
+
@options[:packageDir] = Dir.mktmpdir("disloku")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(name, *args, &block)
|
31
|
+
if (!@options.has_key?(name))
|
32
|
+
raise ArgumentError.new("There's no option '#{name}' here")
|
33
|
+
end
|
34
|
+
|
35
|
+
return @options[name]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative('util/File')
|
2
|
+
|
3
|
+
module Disloku
|
4
|
+
class Repository
|
5
|
+
attr_accessor :location, :root
|
6
|
+
|
7
|
+
def initialize(location)
|
8
|
+
@location = location
|
9
|
+
@root = getRepositoryRoot()
|
10
|
+
@gitDir = File.join(@root, ".git")
|
11
|
+
@provider = getProvider()
|
12
|
+
end
|
13
|
+
|
14
|
+
def getRepositoryRoot()
|
15
|
+
raise NotImplementedError.new()
|
16
|
+
end
|
17
|
+
|
18
|
+
def getBranchName()
|
19
|
+
raise NotImplementedError.new()
|
20
|
+
end
|
21
|
+
|
22
|
+
def getProvider()
|
23
|
+
end
|
24
|
+
|
25
|
+
def getChangeSet(from = nil, to = nil)
|
26
|
+
return @provider.getChangeSet(from, to)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative('Log')
|
2
|
+
require('net/sftp')
|
3
|
+
require('digest/sha1')
|
4
|
+
|
5
|
+
module Disloku
|
6
|
+
class SessionManager
|
7
|
+
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
@sessions = {}
|
12
|
+
|
13
|
+
at_exit { shutdown!() }
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(connection, &block)
|
17
|
+
connection = getConnection(connection)
|
18
|
+
block.call(connection)
|
19
|
+
end
|
20
|
+
|
21
|
+
def getConnection(connection)
|
22
|
+
if (!@sessions.has_key?(connection.hash))
|
23
|
+
Log.instance.info("creating new session #{connection.user}@#{connection.host} -> #{connection.hash}")
|
24
|
+
session = Net::SSH.start(connection.host, connection.user, connection.options)
|
25
|
+
sftp = Net::SFTP::Session.new(session).connect!
|
26
|
+
@sessions[connection.hash] = { :sftp => sftp, :ssh => session }
|
27
|
+
end
|
28
|
+
|
29
|
+
return @sessions[connection.hash][:sftp]
|
30
|
+
end
|
31
|
+
|
32
|
+
def shutdown!()
|
33
|
+
Log.instance.info("closing all open sessions")
|
34
|
+
@sessions.each {|key, value| value[:ssh].close() }
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative('Log');
|
2
|
+
require_relative('SysCmdResult');
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
class SysCmd
|
6
|
+
def initialize(cmd)
|
7
|
+
@cmd = cmd
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute()
|
11
|
+
Log.instance.info("executing '#{@cmd}'")
|
12
|
+
return SysCmdResult.new(%x(#{@cmd}), $?)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative('Mapping')
|
2
|
+
require_relative('Connection')
|
3
|
+
require_relative('util/File')
|
4
|
+
|
5
|
+
module Disloku
|
6
|
+
class Target
|
7
|
+
attr_accessor :name, :connection
|
8
|
+
|
9
|
+
def initialize(name, targetConfig, mappingStore, connectionStore)
|
10
|
+
@name = name
|
11
|
+
@config = targetConfig
|
12
|
+
|
13
|
+
if (@config["connection"].value().kind_of?(String))
|
14
|
+
@connection = connectionStore.get(@config["connection"].value())
|
15
|
+
else
|
16
|
+
@connection = Connection.new(@config["connection"])
|
17
|
+
end
|
18
|
+
|
19
|
+
@mapping = Mapping.new(@config, mappingStore)
|
20
|
+
end
|
21
|
+
|
22
|
+
def mapPath(pathSegments)
|
23
|
+
return @mapping.mapPath(pathSegments)
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(name, *args, &block)
|
27
|
+
if (!@config.has?(name.to_s()))
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
|
31
|
+
return @config[name.to_s()].value()
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative('../ChangeSetProvider');
|
2
|
+
require_relative('../ChangeSet');
|
3
|
+
require_relative('../FileChange');
|
4
|
+
require_relative('../SysCmd');
|
5
|
+
|
6
|
+
module Disloku
|
7
|
+
module Git
|
8
|
+
|
9
|
+
CHANGE_MAP = {
|
10
|
+
' ' => :nochange,
|
11
|
+
'A' => :added,
|
12
|
+
'C' => :copied,
|
13
|
+
'D' => :deleted,
|
14
|
+
'M' => :modified,
|
15
|
+
'R' => :renamed,
|
16
|
+
'U' => :unmerged,
|
17
|
+
'T' => :typechange,
|
18
|
+
'X' => :unknown,
|
19
|
+
'B' => :broken,
|
20
|
+
}
|
21
|
+
|
22
|
+
class ChangeSetProvider < Disloku::ChangeSetProvider
|
23
|
+
def getChangeSet(from = nil, to = nil)
|
24
|
+
if (from == nil)
|
25
|
+
from = "HEAD"
|
26
|
+
end
|
27
|
+
|
28
|
+
if (to == nil)
|
29
|
+
status = SysCmd.new("git diff --name-status --staged #{from} #{repository.root}").execute()
|
30
|
+
else
|
31
|
+
status = SysCmd.new("git diff --name-status #{from} #{to} #{repository.root}").execute()
|
32
|
+
end
|
33
|
+
|
34
|
+
result = ChangeSet.new()
|
35
|
+
status.output.each_line do |line|
|
36
|
+
match = /^(.)\s+(.*)$/.match(line)
|
37
|
+
result << FileChange.new(repository, match[2], getChangeType(match[1]))
|
38
|
+
end
|
39
|
+
|
40
|
+
return result
|
41
|
+
end
|
42
|
+
|
43
|
+
def getChangeType(changeChar)
|
44
|
+
return CHANGE_MAP[changeChar]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative('../Repository')
|
2
|
+
require_relative('../SysCmd')
|
3
|
+
require_relative('ChangeSetProvider')
|
4
|
+
|
5
|
+
module Disloku
|
6
|
+
module Git
|
7
|
+
class Repository < Disloku::Repository
|
8
|
+
def initialize(location)
|
9
|
+
super(location)
|
10
|
+
end
|
11
|
+
|
12
|
+
def getRepositoryRoot()
|
13
|
+
old = Dir.pwd()
|
14
|
+
Dir.chdir(location)
|
15
|
+
status = SysCmd.new("git rev-parse --show-toplevel").execute()
|
16
|
+
Dir.chdir(old)
|
17
|
+
|
18
|
+
return status.output.strip()
|
19
|
+
end
|
20
|
+
|
21
|
+
def getBranchName()
|
22
|
+
branch = SysCmd.new("git --git-dir=\"#{@gitDir}\" rev-parse --abbrev-ref HEAD").execute()
|
23
|
+
return branch.output.strip()
|
24
|
+
end
|
25
|
+
|
26
|
+
def getProvider()
|
27
|
+
return ChangeSetProvider.new(self)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative('../ChangeSetProvider');
|
2
|
+
require_relative('../ChangeSet');
|
3
|
+
require_relative('../FileChange');
|
4
|
+
require_relative('../SysCmd');
|
5
|
+
|
6
|
+
module Disloku
|
7
|
+
module Svn
|
8
|
+
|
9
|
+
CHANGE_MAP = {
|
10
|
+
' ' => :nochange,
|
11
|
+
'A' => :added,
|
12
|
+
'C' => :conflicted,
|
13
|
+
'D' => :deleted,
|
14
|
+
'I' => :ignored,
|
15
|
+
'M' => :modified,
|
16
|
+
'R' => :replaced,
|
17
|
+
'X' => :external,
|
18
|
+
'?' => :unversioned,
|
19
|
+
'!' => :missing,
|
20
|
+
'~' => :obstructed,
|
21
|
+
}
|
22
|
+
|
23
|
+
class ChangeSetProvider < Disloku::ChangeSetProvider
|
24
|
+
def getChangeSet(from, to)
|
25
|
+
status = SysCmd.new("svn status #{repository.root}").execute()
|
26
|
+
|
27
|
+
result = ChangeSet.new()
|
28
|
+
status.output.each_line do |line|
|
29
|
+
match = /^(.)......\s+(.*)$/.match(line)
|
30
|
+
result << FileChange.new(repository, match[2], getChangeType(match[1]))
|
31
|
+
end
|
32
|
+
|
33
|
+
return result
|
34
|
+
end
|
35
|
+
|
36
|
+
def getChangeType(changeChar)
|
37
|
+
return CHANGE_MAP[changeChar]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative('../Repository')
|
2
|
+
require_relative('ChangeSetProvider')
|
3
|
+
|
4
|
+
module Disloku
|
5
|
+
module Svn
|
6
|
+
class Repository < Disloku::Repository
|
7
|
+
def initialize(location)
|
8
|
+
super(location)
|
9
|
+
end
|
10
|
+
|
11
|
+
def getRepositoryRoot()
|
12
|
+
info = %x[svn info --xml #{location}]
|
13
|
+
m = /<wcroot-abspath>(.*)<\/wcroot-abspath>/.match(info)
|
14
|
+
return m[1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def getProvider()
|
18
|
+
return ChangeSetProvider.new(self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative('../Log')
|
2
|
+
require_relative('../BaseTask')
|
3
|
+
require('fileutils')
|
4
|
+
require('stringio')
|
5
|
+
|
6
|
+
module Disloku
|
7
|
+
module Tasks
|
8
|
+
class FolderTask < BaseTask
|
9
|
+
|
10
|
+
def initialize(input)
|
11
|
+
super()
|
12
|
+
@options = getInputParam(input, :options, Options)
|
13
|
+
@changesets = getInputParam(input, :changesets, Array)
|
14
|
+
@target = getInputParam(input, :target, Target)
|
15
|
+
@allowOverride = getInputParam(input, :allowOverride, Object)
|
16
|
+
@deletes = StringIO.new()
|
17
|
+
|
18
|
+
@targetDirectory = File.join(@options.packageDir, @target.name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def beforeExecute()
|
22
|
+
if (!Dir.exists?(@targetDirectory))
|
23
|
+
FileUtils.mkpath(@targetDirectory)
|
24
|
+
elsif (Dir.exists?(@targetDirectory) and !@allowOverride)
|
25
|
+
raise Exception.new("Directory '#{@targetDirectory}' already exists")
|
26
|
+
elsif (Dir.exists?(@targetDirectory))
|
27
|
+
FileUtils.rm_r(@targetDirectory, :force => true)
|
28
|
+
Dir::mkdir(@targetDirectory)
|
29
|
+
end
|
30
|
+
|
31
|
+
@result[:directory] = @targetDirectory
|
32
|
+
@result[:files] = []
|
33
|
+
end
|
34
|
+
|
35
|
+
def executeTask()
|
36
|
+
@changesets.each() do |changeset|
|
37
|
+
changeset.each(&method(:executeOnFileChange))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def executeOnFileChange(change)
|
42
|
+
file = change.getFile(@target)
|
43
|
+
if (!file.hasMapping?())
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
destination = file.getAbsoluteDstPath(@targetDirectory)
|
48
|
+
|
49
|
+
case change.changeType
|
50
|
+
when :modified, :added
|
51
|
+
Log.instance.info("adding file #{file.srcPath}")
|
52
|
+
if (!Dir.exists?(File.dirname(destination)))
|
53
|
+
FileUtils.mkpath(File.dirname(destination))
|
54
|
+
end
|
55
|
+
FileUtils.cp(file.srcPath, destination)
|
56
|
+
when :deleted
|
57
|
+
Log.instance.info("adding file #{file.srcPath} to deletion list")
|
58
|
+
addDelete(file.getAbsoluteDstPath())
|
59
|
+
else
|
60
|
+
Log.instance.warn("ignoring change type #{change.changeType}")
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
@result[:files].push(file)
|
65
|
+
end
|
66
|
+
|
67
|
+
def afterExecute()
|
68
|
+
if (@options.createDeletesFile)
|
69
|
+
File.write(File.join(@targetDirectory, ".deletes"), @deletes.string)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def addDelete(fullPath)
|
74
|
+
@deletes << "#{fullPath}\n"
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative('../DislokuError')
|
2
|
+
require_relative('../Log')
|
3
|
+
require_relative('../BaseTask')
|
4
|
+
require_relative('../SessionManager')
|
5
|
+
|
6
|
+
module Net; module SFTP; module Operations
|
7
|
+
class Upload
|
8
|
+
alias old_on_mkdir on_mkdir
|
9
|
+
def on_mkdir(response)
|
10
|
+
begin
|
11
|
+
old_on_mkdir(response)
|
12
|
+
rescue
|
13
|
+
if (@options[:ignoreMkdirError])
|
14
|
+
process_next_entry
|
15
|
+
else
|
16
|
+
raise
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end; end; end
|
22
|
+
|
23
|
+
module Disloku
|
24
|
+
module Tasks
|
25
|
+
class NetSftpTask < BaseTask
|
26
|
+
|
27
|
+
def initialize(input)
|
28
|
+
super()
|
29
|
+
@repository = getInputParam(input, :repository, Repository)
|
30
|
+
@options = getInputParam(input, :options, Options)
|
31
|
+
@directory = getInputParam(input, :directory, String)
|
32
|
+
@files = getInputParam(input, :files, Array)
|
33
|
+
@target = getInputParam(input, :target, Target)
|
34
|
+
end
|
35
|
+
|
36
|
+
def beforeExecute()
|
37
|
+
if (!@target.branchLock.nil?)
|
38
|
+
branch = @repository.getBranchName()
|
39
|
+
if (branch != @target.branchLock)
|
40
|
+
raise DislokuError.new("Target [#{@target.name}] is locked to branch #{@target.branchLock} but current branch is #{branch}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
puts()
|
45
|
+
puts("Target [#{@target.name}]: #{@target.user}@#{@target.host}:#{@target.targetDir}")
|
46
|
+
@files.each() do |file|
|
47
|
+
puts(file)
|
48
|
+
end
|
49
|
+
puts()
|
50
|
+
puts("Continue with deployment (Y/N)?")
|
51
|
+
response = STDIN.readline().chomp()
|
52
|
+
@skip = response.match(/^[Yy]/) == nil
|
53
|
+
puts("skipping: #{@skip}")
|
54
|
+
end
|
55
|
+
|
56
|
+
def executeTask()
|
57
|
+
if (@skip)
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
SessionManager.instance.get(@target.connection) do |sftp|
|
62
|
+
Log.instance.info("copying new files...")
|
63
|
+
sftp.upload!(@directory, @target.targetDir, { :ignoreMkdirError => true }) do |event, uploader, *args|
|
64
|
+
case event
|
65
|
+
when :open then
|
66
|
+
# args[0] : file metadata
|
67
|
+
# "starting upload: #{args[0].local} -> #{args[0].remote} (#{args[0].size} bytes}"
|
68
|
+
print(".")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
puts()
|
72
|
+
|
73
|
+
@files.each() do |file|
|
74
|
+
if (file.change.changeType == :deleted)
|
75
|
+
path = file.getAbsoluteDstPath()
|
76
|
+
Log.instance.info("deleting file #{path}")
|
77
|
+
begin
|
78
|
+
sftp.remove!(path)
|
79
|
+
rescue
|
80
|
+
if (!@options.ignoreDeleteErrors)
|
81
|
+
raise
|
82
|
+
else
|
83
|
+
Log.instance.info("unable to delete file #{path} (it probably doesn't exist)")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def afterExecute()
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative('../Log')
|
2
|
+
require_relative('../BaseTask')
|
3
|
+
require('Open3')
|
4
|
+
|
5
|
+
module Disloku
|
6
|
+
module Tasks
|
7
|
+
# class PsFtpTask < BaseTask
|
8
|
+
# def initialize(stream, config, changesets)
|
9
|
+
# super(stream, changesets)
|
10
|
+
# @config = config.yaml["psftp"]
|
11
|
+
# end
|
12
|
+
|
13
|
+
# def beforeExecute()
|
14
|
+
# Log.instance.info(@config["path"])
|
15
|
+
# env = @config["env"][0]
|
16
|
+
# Log.instance.info(env)
|
17
|
+
# cmd = "\"#{@config['path']}\""
|
18
|
+
|
19
|
+
# if (env.has_key?("key"))
|
20
|
+
# cmd += " -i \"#{env['key']}\""
|
21
|
+
# else
|
22
|
+
# cmd += " -pw #{env['password']}"
|
23
|
+
# end
|
24
|
+
|
25
|
+
# cmd += " #{env['user']}@#{env['host']}"
|
26
|
+
|
27
|
+
# Log.instance.info(cmd)
|
28
|
+
|
29
|
+
# @stream, @output = Open3.popen2(cmd)
|
30
|
+
# writeLine("cd #{env['targetDir']}")
|
31
|
+
# end
|
32
|
+
|
33
|
+
# def executeTask(changeset)
|
34
|
+
# changeset.each() do |change|
|
35
|
+
# destination = change.file.segments.join("/")
|
36
|
+
# case change.changeType
|
37
|
+
# when :modified, :added
|
38
|
+
# path = change.file.getPathSegments().join('/')
|
39
|
+
# Log.instance.info("mkdir \"#{path}\"")
|
40
|
+
# writeLine("mkdir \"#{path}\"")
|
41
|
+
# Log.instance.info("put \"#{change.file.filePath}\" \"#{destination}\"")
|
42
|
+
# writeLine("put \"#{change.file.filePath}\" \"#{destination}\"")
|
43
|
+
# when :deleted
|
44
|
+
# writeLine("del \"#{destination}\"")
|
45
|
+
# else
|
46
|
+
# Log.instance.info("ignoring change type #{change.changeType}")
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
|
51
|
+
# def afterExecute()
|
52
|
+
# writeLine("quit")
|
53
|
+
|
54
|
+
# @output.readlines.each(&Log.instance.method(:info))
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Disloku
|
2
|
+
module Util
|
3
|
+
|
4
|
+
SPLIT_EXP = "\\#{File::SEPARATOR}|\\#{File::ALT_SEPARATOR}"
|
5
|
+
|
6
|
+
class File
|
7
|
+
attr_accessor :srcPath, :change
|
8
|
+
|
9
|
+
def initialize(filePath, basePath, target, change)
|
10
|
+
@srcPath = filePath
|
11
|
+
@target = target
|
12
|
+
@change = change
|
13
|
+
|
14
|
+
fileSegments = filePath.split(/#{SPLIT_EXP}/)
|
15
|
+
baseSegments = basePath.split(/#{SPLIT_EXP}/)
|
16
|
+
index = 0
|
17
|
+
while (fileSegments[index] == baseSegments[index])
|
18
|
+
index += 1
|
19
|
+
end
|
20
|
+
|
21
|
+
@relativeSrcSegments = fileSegments[index..-1]
|
22
|
+
@relativeDstSegments = target.mapPath(@relativeSrcSegments)
|
23
|
+
end
|
24
|
+
|
25
|
+
def getRelativeDstSegments()
|
26
|
+
return @segments
|
27
|
+
end
|
28
|
+
|
29
|
+
def getRelativeDirSegments()
|
30
|
+
return @segments[0..-2]
|
31
|
+
end
|
32
|
+
|
33
|
+
def hasMapping?()
|
34
|
+
return @relativeDstSegments != nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def getAbsoluteDstPath(basePath = nil)
|
38
|
+
basePath = basePath || @target.targetDir
|
39
|
+
return ::File.join(basePath, *@relativeDstSegments)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s()
|
43
|
+
return "#{srcPath} -> #{getAbsoluteDstPath()}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/disloku.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative('disloku/Disloku')
|
2
|
+
require_relative('disloku/SysCmd')
|
3
|
+
require_relative('disloku/SessionManager')
|
4
|
+
require('thor')
|
5
|
+
|
6
|
+
class DislokuCli < Thor
|
7
|
+
desc "deploy [FROM] [TO]", "deploy changes"
|
8
|
+
method_option :dir, :default => ".", :aliases => "-d", :desc => "repository directory"
|
9
|
+
method_option :target, :aliases => "-t", :desc => "target"
|
10
|
+
def deploy(from = nil, to = nil)
|
11
|
+
puts "deploy #{options.inspect}"
|
12
|
+
|
13
|
+
disloku = Disloku::Disloku.new(options)
|
14
|
+
disloku.deployPackage(from, to)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "build [FROM] [TO]", "build change package"
|
18
|
+
method_option :dir, :default => ".", :aliases => "-d", :desc => "repository directory"
|
19
|
+
method_option :target, :aliases => "-t", :desc => "target"
|
20
|
+
def build(from = nil, to = nil)
|
21
|
+
p(options)
|
22
|
+
disloku = Disloku::Disloku.new(options)
|
23
|
+
dir = disloku.buildPackage(from, to)
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "config", "show configuration"
|
27
|
+
method_option :dir, :default => ".", :aliases => "-d", :desc => "repository directory"
|
28
|
+
def config()
|
29
|
+
disloku = Disloku::Disloku.new(options)
|
30
|
+
puts(disloku.config.to_yaml())
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: disloku
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lukas Angerer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A deployment tool that allows copying of Git changesets via sftp
|
14
|
+
email: executor.dev@gmail.com
|
15
|
+
executables:
|
16
|
+
- disloku
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/disloku/BaseTask.rb
|
21
|
+
- lib/disloku/ChangeSet.rb
|
22
|
+
- lib/disloku/ChangeSetProvider.rb
|
23
|
+
- lib/disloku/Commit.rb
|
24
|
+
- lib/disloku/Config.rb
|
25
|
+
- lib/disloku/Connection.rb
|
26
|
+
- lib/disloku/ConnectionStore.rb
|
27
|
+
- lib/disloku/Disloku.rb
|
28
|
+
- lib/disloku/DislokuError.rb
|
29
|
+
- lib/disloku/FileChange.rb
|
30
|
+
- lib/disloku/git/ChangeSetProvider.rb
|
31
|
+
- lib/disloku/git/Repository.rb
|
32
|
+
- lib/disloku/Log.rb
|
33
|
+
- lib/disloku/Mapping.rb
|
34
|
+
- lib/disloku/MappingStore.rb
|
35
|
+
- lib/disloku/NamedConfigStore.rb
|
36
|
+
- lib/disloku/Options.rb
|
37
|
+
- lib/disloku/Repository.rb
|
38
|
+
- lib/disloku/SessionManager.rb
|
39
|
+
- lib/disloku/svn/ChangeSetProvider.rb
|
40
|
+
- lib/disloku/svn/Repository.rb
|
41
|
+
- lib/disloku/SysCmd.rb
|
42
|
+
- lib/disloku/SysCmdResult.rb
|
43
|
+
- lib/disloku/Target.rb
|
44
|
+
- lib/disloku/tasks/FolderTask.rb
|
45
|
+
- lib/disloku/tasks/NetSftpTask.rb
|
46
|
+
- lib/disloku/tasks/PsFtpTask.rb
|
47
|
+
- lib/disloku/util/File.rb
|
48
|
+
- lib/disloku/util/Hash.rb
|
49
|
+
- lib/disloku.rb
|
50
|
+
- bin/disloku
|
51
|
+
homepage:
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
metadata: {}
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 2.0.14
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: Disloku deployment tool
|
75
|
+
test_files: []
|