disloku 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 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,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require('disloku')
4
+ DislokuCli.start()
@@ -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,14 @@
1
+ require 'Forwardable'
2
+ require 'Pathname'
3
+
4
+ module Disloku
5
+ class ChangeSet
6
+ include Enumerable
7
+ extend Forwardable
8
+ def_delegators :@changes, :each, :<<
9
+
10
+ def initialize()
11
+ @changes = Array.new()
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+
2
+ module Disloku
3
+ class ChangeSetProvider
4
+ attr_accessor :repository
5
+
6
+ def initialize(repository)
7
+ @repository = repository
8
+ end
9
+
10
+ def getChangeSet(from, to)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+
2
+ module Disloku
3
+ class Commit
4
+ def initialize()
5
+ end
6
+ end
7
+ 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,5 @@
1
+
2
+ module Disloku
3
+ class DislokuError < StandardError
4
+ end
5
+ 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
@@ -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,12 @@
1
+ require_relative('Log');
2
+
3
+ module Disloku
4
+ class SysCmdResult
5
+ attr_accessor :output, :exitCode, :result
6
+ def initialize(output, result)
7
+ @output = output
8
+ @result = result
9
+ @exitCode = result.to_i()
10
+ end
11
+ end
12
+ 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
@@ -0,0 +1,12 @@
1
+
2
+ class Hash
3
+ def recursive_merge(other)
4
+ return merge(other) do |key, oldv, newv|
5
+ if (oldv.kind_of?(Hash) && newv.kind_of?(Hash))
6
+ oldv.recursive_merge(newv)
7
+ else
8
+ newv
9
+ end
10
+ end
11
+ end
12
+ 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: []