disloku 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []