winrm-fs 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/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENSE +202 -0
- data/README.md +62 -0
- data/Rakefile +30 -0
- data/VERSION +1 -0
- data/Vagrantfile +9 -0
- data/bin/rwinrmcp +86 -0
- data/changelog.md +1 -0
- data/lib/winrm-fs.rb +27 -0
- data/lib/winrm-fs/core/command_executor.rb +69 -0
- data/lib/winrm-fs/core/file_uploader.rb +84 -0
- data/lib/winrm-fs/core/temp_zip_file.rb +95 -0
- data/lib/winrm-fs/core/upload_orchestrator.rb +118 -0
- data/lib/winrm-fs/exceptions.rb +28 -0
- data/lib/winrm-fs/file_manager.rb +123 -0
- data/lib/winrm-fs/scripts/checksum.ps1.erb +13 -0
- data/lib/winrm-fs/scripts/create_dir.ps1.erb +6 -0
- data/lib/winrm-fs/scripts/decode_file.ps1.erb +36 -0
- data/lib/winrm-fs/scripts/delete.ps1.erb +6 -0
- data/lib/winrm-fs/scripts/download.ps1.erb +7 -0
- data/lib/winrm-fs/scripts/exists.ps1.erb +8 -0
- data/lib/winrm-fs/scripts/scripts.rb +31 -0
- data/spec/config-example.yml +5 -0
- data/spec/file_manager_spec.rb +140 -0
- data/spec/matchers.rb +54 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/temp_zip_file_spec.rb +71 -0
- data/winrm-fs.gemspec +36 -0
- metadata +189 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
$p = $ExecutionContext.SessionState.Path
|
2
|
+
$path = $p.GetUnresolvedProviderPathFromPSPath("<%= path %>")
|
3
|
+
|
4
|
+
if (Test-Path $path -PathType Leaf) {
|
5
|
+
$cryptoProv = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
|
6
|
+
$file = [System.IO.File]::Open($path,
|
7
|
+
[System.IO.Filemode]::Open, [System.IO.FileAccess]::Read)
|
8
|
+
$md5 = ([System.BitConverter]::ToString($cryptoProv.ComputeHash($file)))
|
9
|
+
$md5 = $md5.Replace("-","").ToLower()
|
10
|
+
$file.Close()
|
11
|
+
|
12
|
+
Write-Host $md5
|
13
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
$path = $ExecutionContext.SessionState.Path
|
2
|
+
$tempFile = $path.GetUnresolvedProviderPathFromPSPath("<%= src %>")
|
3
|
+
$dest = $path.GetUnresolvedProviderPathFromPSPath("<%= dest %>")
|
4
|
+
|
5
|
+
function Decode-File($encodedFile, $decodedFile) {
|
6
|
+
if (Test-Path $encodedFile) {
|
7
|
+
$base64Content = Get-Content $encodedFile
|
8
|
+
}
|
9
|
+
if ($base64Content -eq $null) {
|
10
|
+
New-Item -ItemType file -Force $decodedFile | Out-Null
|
11
|
+
}
|
12
|
+
else {
|
13
|
+
$bytes = [System.Convert]::FromBase64String($base64Content)
|
14
|
+
[System.IO.File]::WriteAllBytes($decodedFile, $bytes) | Out-Null
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
function Ensure-Dir-Exists($path) {
|
19
|
+
# ensure the destination directory exists
|
20
|
+
if (!(Test-Path $path)) {
|
21
|
+
New-Item -ItemType Directory -Force -Path $path | Out-Null
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
if ([System.IO.Path]::GetExtension($tempFile) -eq '.zip') {
|
26
|
+
Ensure-Dir-Exists $dest
|
27
|
+
Decode-File $tempFile $tempFile
|
28
|
+
$shellApplication = New-Object -com shell.application
|
29
|
+
$zipPackage = $shellApplication.NameSpace($tempFile)
|
30
|
+
$destinationFolder = $shellApplication.NameSpace($dest)
|
31
|
+
$destinationFolder.CopyHere($zipPackage.Items(), 0x10) | Out-Null
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
Ensure-Dir-Exists ([System.IO.Path]::GetDirectoryName($dest))
|
35
|
+
Decode-File $tempFile $dest
|
36
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'erubis'
|
18
|
+
|
19
|
+
module WinRM
|
20
|
+
module FS
|
21
|
+
# PS1 scripts
|
22
|
+
module Scripts
|
23
|
+
def self.render(template, context)
|
24
|
+
template_path = File.expand_path(
|
25
|
+
"#{File.dirname(__FILE__)}/#{template}.ps1.erb")
|
26
|
+
template = File.read(template_path)
|
27
|
+
Erubis::Eruby.new(template).result(context)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
describe WinRM::FS::FileManager, integration: true do
|
3
|
+
let(:dest_dir) { File.join(subject.temp_dir, "winrm_#{rand(2**16)}") }
|
4
|
+
let(:temp_upload_dir) { '$env:TEMP/winrm-upload' }
|
5
|
+
let(:src_dir) { File.expand_path(File.dirname(__FILE__)) }
|
6
|
+
let(:service) { winrm_connection }
|
7
|
+
|
8
|
+
subject { WinRM::FS::FileManager.new(service) }
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
expect(subject.delete(dest_dir)).to be true
|
12
|
+
expect(subject.delete(temp_upload_dir)).to be true
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'exists?' do
|
16
|
+
it 'should exist' do
|
17
|
+
expect(subject.exists?('c:/windows')).to be true
|
18
|
+
expect(subject.exists?('c:/foobar')).to be false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'create and delete dir' do
|
23
|
+
it 'should create the directory recursively' do
|
24
|
+
subdir = File.join(dest_dir, 'subdir1', 'subdir2')
|
25
|
+
expect(subject.create_dir(subdir)).to be true
|
26
|
+
expect(subject.exists?(subdir)).to be true
|
27
|
+
expect(subject.create_dir(subdir)).to be true
|
28
|
+
expect(subject.delete(subdir)).to be true
|
29
|
+
expect(subject.exists?(subdir)).to be false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'temp_dir' do
|
34
|
+
it 'should return the remote users temp dir' do
|
35
|
+
expect(subject.temp_dir).to match(%r{C:/Users/\w+/AppData/Local/Temp})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'upload file' do
|
40
|
+
let(:src_file) { __FILE__ }
|
41
|
+
let(:dest_file) { File.join(dest_dir, File.basename(src_file)) }
|
42
|
+
|
43
|
+
before(:each) do
|
44
|
+
expect(subject.delete(dest_dir)).to be true
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should upload the file to the specified file' do
|
48
|
+
subject.upload(src_file, dest_file)
|
49
|
+
expect(subject).to have_created(dest_file).with_content(src_file)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should upload the file to the specified directory' do
|
53
|
+
subject.upload(src_file, dest_dir)
|
54
|
+
expect(subject).to have_created(dest_file).with_content(src_file)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should upload the file to the specified directory with env var' do
|
58
|
+
subject.upload(src_file, '$env:Temp')
|
59
|
+
expected_dest_file = File.join(subject.temp_dir, File.basename(src_file))
|
60
|
+
expect(subject).to have_created(expected_dest_file).with_content(src_file)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should upload the file to the specified nested directory' do
|
64
|
+
dest_sub_dir = File.join(dest_dir, 'subdir')
|
65
|
+
dest_sub_dir_file = File.join(dest_sub_dir, File.basename(src_file))
|
66
|
+
subject.upload(src_file, dest_sub_dir)
|
67
|
+
expect(subject).to have_created(dest_sub_dir_file).with_content(src_file)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'yields progress data' do
|
71
|
+
total = subject.upload(src_file, dest_file) do \
|
72
|
+
|bytes_copied, total_bytes, local_path, remote_path|
|
73
|
+
expect(total_bytes).to be > 0
|
74
|
+
expect(bytes_copied).to eq(total_bytes)
|
75
|
+
expect(local_path).to eq(src_file)
|
76
|
+
expect(remote_path).to eq(dest_file)
|
77
|
+
end
|
78
|
+
expect(total).to be > 0
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should not upload the file when content matches' do
|
82
|
+
subject.upload(src_file, dest_dir)
|
83
|
+
bytes_uploaded = subject.upload(src_file, dest_dir)
|
84
|
+
expect(bytes_uploaded).to eq 0
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should upload file when content differs' do
|
88
|
+
another_src_file = File.join(src_dir, 'matchers.rb')
|
89
|
+
subject.upload(another_src_file, dest_file)
|
90
|
+
bytes_uploaded = subject.upload(src_file, dest_file)
|
91
|
+
expect(bytes_uploaded).to be > 0
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'raises WinRMUploadError when a bad source path is specified' do
|
95
|
+
expect { subject.upload('c:/some/non-existant/path/foo', dest_file) }.to raise_error
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'upload empty file' do
|
100
|
+
let(:src_file) { __FILE__ }
|
101
|
+
let(:empty_src_file) { Tempfile.new('empty').path }
|
102
|
+
let(:dest_file) { File.join(dest_dir, 'emptyfile.txt') }
|
103
|
+
|
104
|
+
it 'creates a new empty file' do
|
105
|
+
expect(subject.upload(empty_src_file, dest_file)).to be 0
|
106
|
+
expect(subject).to have_created(dest_file).with_content('')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'overwrites an existing file' do
|
110
|
+
expect(subject.upload(src_file, dest_file)).to be > 0
|
111
|
+
expect(subject.upload(empty_src_file, dest_file)).to be 0
|
112
|
+
expect(subject).to have_created(dest_file).with_content('')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'upload directory' do
|
117
|
+
it 'copies the entire directory' do
|
118
|
+
bytes_uploaded = subject.upload(src_dir, dest_dir)
|
119
|
+
expect(bytes_uploaded).to be > 0
|
120
|
+
Dir.glob(src_dir + '/*.rb').each do |host_file|
|
121
|
+
host_file_rel = host_file[src_dir.length..-1]
|
122
|
+
remote_file = File.join(dest_dir, host_file_rel)
|
123
|
+
expect(subject).to have_created(remote_file).with_content(host_file)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'does not copy the directory when content is the same' do
|
128
|
+
subject.upload(src_dir, dest_dir)
|
129
|
+
bytes_uploaded = subject.upload(src_dir, dest_dir)
|
130
|
+
expect(bytes_uploaded).to eq 0
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'copies the directory when content differs' do
|
134
|
+
subject.upload(src_dir, dest_dir)
|
135
|
+
another_dir = File.dirname(src_dir)
|
136
|
+
bytes_uploaded = subject.upload(another_dir, dest_dir)
|
137
|
+
expect(bytes_uploaded).to be > 0
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/spec/matchers.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rspec/expectations'
|
3
|
+
|
4
|
+
RSpec::Matchers.define :have_created do |remote_file|
|
5
|
+
match do |file_manager|
|
6
|
+
if @expected_content
|
7
|
+
downloaded_file = Tempfile.new('downloaded')
|
8
|
+
downloaded_file.close
|
9
|
+
|
10
|
+
subject.download(remote_file, downloaded_file.path)
|
11
|
+
@actual_content = File.read(downloaded_file.path)
|
12
|
+
downloaded_file.delete
|
13
|
+
|
14
|
+
file_manager.exists?(remote_file) && \
|
15
|
+
@actual_content == @expected_content
|
16
|
+
else
|
17
|
+
file_manager.exists?(remote_file)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
chain :with_content do |expected_content|
|
21
|
+
expected_content = File.read(expected_content) if File.file?(expected_content)
|
22
|
+
@expected_content = expected_content
|
23
|
+
end
|
24
|
+
failure_message do
|
25
|
+
if @expected_content
|
26
|
+
<<-EOH
|
27
|
+
Expected file '#{remote_file}' to exist with content:
|
28
|
+
|
29
|
+
#{@expected_content}
|
30
|
+
|
31
|
+
but instead got content:
|
32
|
+
|
33
|
+
#{@actual_content}
|
34
|
+
EOH
|
35
|
+
else
|
36
|
+
"Expected file '#{remote_file}' to exist"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
RSpec::Matchers.define :contain_zip_entries do |zip_entries|
|
42
|
+
match do |temp_zip_file|
|
43
|
+
zip_entries = [zip_entries] if zip_entries.is_a? String
|
44
|
+
zip_file = Zip::File.open(temp_zip_file.path)
|
45
|
+
@missing_entries = []
|
46
|
+
zip_entries.each do |entry|
|
47
|
+
@missing_entries << entry unless zip_file.find_entry(entry)
|
48
|
+
end
|
49
|
+
@missing_entries.empty?
|
50
|
+
end
|
51
|
+
failure_message do |temp_zip_file|
|
52
|
+
"Expected #{temp_zip_file.path} to contain zip entries: #{@missing_entries}"
|
53
|
+
end
|
54
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'winrm-fs'
|
5
|
+
require 'json'
|
6
|
+
require_relative 'matchers'
|
7
|
+
|
8
|
+
# Creates a WinRM connection for integration tests
|
9
|
+
module ConnectionHelper
|
10
|
+
def winrm_connection
|
11
|
+
config = symbolize_keys(YAML.load(File.read(winrm_config_path)))
|
12
|
+
config[:options].merge!(basic_auth_only: true) unless config[:auth_type].eql? :kerberos
|
13
|
+
winrm = WinRM::WinRMWebService.new(
|
14
|
+
config[:endpoint], config[:auth_type].to_sym, config[:options])
|
15
|
+
winrm
|
16
|
+
end
|
17
|
+
|
18
|
+
def winrm_config_path
|
19
|
+
# Copy config-example.yml to config.yml and edit for your local configuration
|
20
|
+
path = File.expand_path("#{File.dirname(__FILE__)}/config.yml")
|
21
|
+
unless File.exist?(path)
|
22
|
+
# user hasn't done this, so use sane defaults for unit tests
|
23
|
+
path = File.expand_path("#{File.dirname(__FILE__)}/config-example.yml")
|
24
|
+
end
|
25
|
+
path
|
26
|
+
end
|
27
|
+
|
28
|
+
# rubocop:disable Metrics/MethodLength
|
29
|
+
def symbolize_keys(hash)
|
30
|
+
hash.each_with_object({}) do |(key, value), result|
|
31
|
+
new_key = case key
|
32
|
+
when String then key.to_sym
|
33
|
+
else key
|
34
|
+
end
|
35
|
+
new_value = case value
|
36
|
+
when Hash then symbolize_keys(value)
|
37
|
+
else value
|
38
|
+
end
|
39
|
+
result[new_key] = new_value
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/MethodLength
|
44
|
+
end
|
45
|
+
|
46
|
+
RSpec.configure do |config|
|
47
|
+
config.include(ConnectionHelper)
|
48
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'zip'
|
3
|
+
require_relative '../lib/winrm-fs/core/temp_zip_file'
|
4
|
+
|
5
|
+
describe WinRM::FS::Core::TempZipFile, integration: true do
|
6
|
+
let(:src_dir) { File.expand_path('../lib/winrm-fs', File.dirname(__FILE__)) }
|
7
|
+
let(:src_file) { __FILE__ }
|
8
|
+
|
9
|
+
subject { WinRM::FS::Core::TempZipFile.new }
|
10
|
+
after(:each) { subject.delete }
|
11
|
+
|
12
|
+
context 'temp file creation' do
|
13
|
+
it 'should create a temp file on disk' do
|
14
|
+
expect(File.exist?(subject.path)).to be true
|
15
|
+
subject.delete
|
16
|
+
expect(File.exist?(subject.path)).to be false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'add_file' do
|
21
|
+
it 'should raise error when file doesn not exist' do
|
22
|
+
expect { subject.add_file('/etc/foo/does/not/exist') }.to \
|
23
|
+
raise_error('/etc/foo/does/not/exist isn\'t a file')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should raise error when file is a directory' do
|
27
|
+
dir = File.dirname(subject.path)
|
28
|
+
expect { subject.add_file(dir) }.to \
|
29
|
+
raise_error("#{dir} isn\'t a file")
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should add a file to the zip' do
|
33
|
+
subject.add_file(src_file)
|
34
|
+
expect(subject).to contain_zip_entries(File.basename(src_file))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'add_directory' do
|
39
|
+
it 'should raise error when directory does not exist' do
|
40
|
+
expect { subject.add_directory('/etc/does/not/exist') }.to \
|
41
|
+
raise_error('/etc/does/not/exist isn\'t a directory')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should raise error when directory is a file' do
|
45
|
+
expect { subject.add_directory(subject.path) }.to \
|
46
|
+
raise_error("#{subject.path} isn\'t a directory")
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should add all files in directory to the zip recursively' do
|
50
|
+
subject.add_directory(src_dir)
|
51
|
+
expect(subject).to contain_zip_entries(['exceptions.rb', 'core/temp_zip_file.rb'])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'add' do
|
56
|
+
it 'should add all files when given a directory' do
|
57
|
+
subject.add(src_dir)
|
58
|
+
expect(subject).to contain_zip_entries(['exceptions.rb', 'core/temp_zip_file.rb'])
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should add a file when given only a file' do
|
62
|
+
subject.add(src_file)
|
63
|
+
expect(subject).to contain_zip_entries(File.basename(src_file))
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should raise error when given a non-path' do
|
67
|
+
expect { subject.add('garbage') }.to \
|
68
|
+
raise_error("garbage doesn't exist")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/winrm-fs.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
version = File.read(File.expand_path('../VERSION', __FILE__)).strip
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.name = 'winrm-fs'
|
9
|
+
s.version = version
|
10
|
+
s.date = Date.today.to_s
|
11
|
+
|
12
|
+
s.author = ['Shawn Neal', 'Matt Wrock']
|
13
|
+
s.email = ['sneal@sneal.net', 'matt@mattwrock.com']
|
14
|
+
s.homepage = 'http://github.com/WinRb/winrm-fs'
|
15
|
+
|
16
|
+
s.summary = 'WinRM File System'
|
17
|
+
s.description = <<-EOF
|
18
|
+
Ruby library for file system operations via Windows Remote Management
|
19
|
+
EOF
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split(/\n/)
|
22
|
+
s.require_path = 'lib'
|
23
|
+
s.rdoc_options = %w(-x test/ -x examples/)
|
24
|
+
s.extra_rdoc_files = %w(README.md LICENSE)
|
25
|
+
|
26
|
+
s.bindir = 'bin'
|
27
|
+
s.executables = ['rwinrmcp']
|
28
|
+
s.required_ruby_version = '>= 1.9.0'
|
29
|
+
s.add_runtime_dependency 'erubis', '~> 2.7'
|
30
|
+
s.add_runtime_dependency 'logging', '~> 1.6', '>= 1.6.1'
|
31
|
+
s.add_runtime_dependency 'rubyzip', '~> 1.1'
|
32
|
+
s.add_runtime_dependency 'winrm', '~> 1.3.0'
|
33
|
+
s.add_development_dependency 'rspec', '~> 3.0.0'
|
34
|
+
s.add_development_dependency 'rake', '~> 10.3.2'
|
35
|
+
s.add_development_dependency 'rubocop', '~> 0.28.0'
|
36
|
+
end
|