rptman 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/LICENSE +176 -0
- data/README.rdoc +105 -0
- data/lib/rptman.rb +14 -0
- data/lib/ssrs/UUID.rb +326 -0
- data/lib/ssrs/bids.rb +35 -0
- data/lib/ssrs/config.rb +139 -0
- data/lib/ssrs/datasource.rb +41 -0
- data/lib/ssrs/report.rb +35 -0
- data/lib/ssrs/report_project.rb +83 -0
- data/lib/ssrs/shell.rb +77 -0
- data/lib/ssrs/ssrs-api.jar +0 -0
- data/lib/ssrs/ssrs.rb +6 -0
- data/lib/ssrs/uploader.rb +32 -0
- data/lib/tasks/rptman.rake +20 -0
- data/rptman.gemspec +20 -0
- metadata +75 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
Apache License
|
2
|
+
Version 2.0, January 2004
|
3
|
+
http://www.apache.org/licenses/
|
4
|
+
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6
|
+
|
7
|
+
1. Definitions.
|
8
|
+
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
11
|
+
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13
|
+
the copyright owner that is granting the License.
|
14
|
+
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
16
|
+
other entities that control, are controlled by, or are under common
|
17
|
+
control with that entity. For the purposes of this definition,
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
19
|
+
direction or management of such entity, whether by contract or
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22
|
+
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24
|
+
exercising permissions granted by this License.
|
25
|
+
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
27
|
+
including but not limited to software source code, documentation
|
28
|
+
source, and configuration files.
|
29
|
+
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
31
|
+
transformation or translation of a Source form, including but
|
32
|
+
not limited to compiled object code, generated documentation,
|
33
|
+
and conversions to other media types.
|
34
|
+
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
36
|
+
Object form, made available under the License, as indicated by a
|
37
|
+
copyright notice that is included in or attached to the work
|
38
|
+
(an example is provided in the Appendix below).
|
39
|
+
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46
|
+
the Work and Derivative Works thereof.
|
47
|
+
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
49
|
+
the original version of the Work and any modifications or additions
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
61
|
+
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
64
|
+
subsequently incorporated within the Work.
|
65
|
+
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
72
|
+
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78
|
+
where such license applies only to those patent claims licensable
|
79
|
+
by such Contributor that are necessarily infringed by their
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
82
|
+
institute patent litigation against any entity (including a
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
85
|
+
or contributory patent infringement, then any patent licenses
|
86
|
+
granted to You under this License for that Work shall terminate
|
87
|
+
as of the date such litigation is filed.
|
88
|
+
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
91
|
+
modifications, and in Source or Object form, provided that You
|
92
|
+
meet the following conditions:
|
93
|
+
|
94
|
+
(a) You must give any other recipients of the Work or
|
95
|
+
Derivative Works a copy of this License; and
|
96
|
+
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
98
|
+
stating that You changed the files; and
|
99
|
+
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
102
|
+
attribution notices from the Source form of the Work,
|
103
|
+
excluding those notices that do not pertain to any part of
|
104
|
+
the Derivative Works; and
|
105
|
+
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
108
|
+
include a readable copy of the attribution notices contained
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
111
|
+
of the following places: within a NOTICE text file distributed
|
112
|
+
as part of the Derivative Works; within the Source form or
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
114
|
+
within a display generated by the Derivative Works, if and
|
115
|
+
wherever such third-party notices normally appear. The contents
|
116
|
+
of the NOTICE file are for informational purposes only and
|
117
|
+
do not modify the License. You may add Your own attribution
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
120
|
+
that such additional attribution notices cannot be construed
|
121
|
+
as modifying the License.
|
122
|
+
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
124
|
+
may provide additional or different license terms and conditions
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
128
|
+
the conditions stated in this License.
|
129
|
+
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
133
|
+
this License, without any additional terms or conditions.
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135
|
+
the terms of any separate license agreement you may have executed
|
136
|
+
with Licensor regarding such Contributions.
|
137
|
+
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
140
|
+
except as required for reasonable and customary use in describing the
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
142
|
+
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
152
|
+
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
158
|
+
incidental, or consequential damages of any character arising as a
|
159
|
+
result of this License or out of the use or inability to use the
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
162
|
+
other commercial damages or losses), even if such Contributor
|
163
|
+
has been advised of the possibility of such damages.
|
164
|
+
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168
|
+
or other liability obligations and/or rights consistent with this
|
169
|
+
License. However, in accepting such obligations, You may act only
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
174
|
+
of your accepting any such warranty or additional liability.
|
175
|
+
|
176
|
+
END OF TERMS AND CONDITIONS
|
data/README.rdoc
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
= rptman
|
2
|
+
|
3
|
+
This is a command line tool and suite of rake tasks for uploading SSRS
|
4
|
+
reports to a server. The tool can also generate project files for
|
5
|
+
the "SQL Server Business Intelligence Development Studio".
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
The extension is packaged as a jruby gem named "rptman", consult the ruby
|
10
|
+
gems installation steps but typically it is
|
11
|
+
|
12
|
+
jgem install rptman
|
13
|
+
|
14
|
+
== Basic Overview
|
15
|
+
|
16
|
+
Reports are stored in sub-directories of a <report-dir>. The filename
|
17
|
+
of the report must end with ".rdl". The directory hierarchy on the local
|
18
|
+
file system is mirrored on the SSRS server during an upload. So if a file
|
19
|
+
exists with the name
|
20
|
+
|
21
|
+
<report-dir>/IRIS/Coordination/Tour Of Duty Report.rdl
|
22
|
+
|
23
|
+
It will be uploaded to the SSRS server with the path
|
24
|
+
|
25
|
+
/IRIS/Coordination/Tour Of Duty Report
|
26
|
+
|
27
|
+
Every top level directory on the local filesystem that includes a report
|
28
|
+
file is deleted when it during the upload process. So in the above scenario
|
29
|
+
the /IRIS directory on SSRS server is delted prior to uploading the
|
30
|
+
directory. It is assumed that every report in a particular hierarhcy is
|
31
|
+
stored in one location.
|
32
|
+
|
33
|
+
The tool can also create data source definitions and these are placed in a
|
34
|
+
directory named "/DataSources".
|
35
|
+
|
36
|
+
The gem supports prefixing all paths with when uploading to the SSRS server.
|
37
|
+
It is possible to prefix all reports managed by this tool with /Auto so as
|
38
|
+
to distinguish them from reports uploaded through other means. If multiple
|
39
|
+
people are working of the same SSRS instance it is also possible to decorate
|
40
|
+
the prefix with a username or environment. (i.e. /Auto/PD42/DEV)
|
41
|
+
|
42
|
+
The configuration data for determing which SSRS instance and prefix to use
|
43
|
+
is in a yaml file with a format described below. The easiest way to use the
|
44
|
+
tool is to create a ruby script such as the following;
|
45
|
+
|
46
|
+
gem 'rptman'
|
47
|
+
|
48
|
+
# The configuration file
|
49
|
+
SSRS::Config.config_filename = "database.yml"
|
50
|
+
|
51
|
+
# The directory in which the reports are stored
|
52
|
+
SSRS::Config.reports_dir = "reports"
|
53
|
+
|
54
|
+
# Define a data source named IRIS_CENTRAL that has
|
55
|
+
# configration data stored under the key 'central'
|
56
|
+
SSRS::Config.define_datasource('IRIS_CENTRAL','central')
|
57
|
+
|
58
|
+
# actually run the tool
|
59
|
+
SSRS::Shell.run
|
60
|
+
|
61
|
+
The script can then be run with a -h parameter to see the various options.
|
62
|
+
|
63
|
+
== Configuration Format
|
64
|
+
|
65
|
+
The configuration is stored in a yaml file. The configuration file format
|
66
|
+
allows for multiple "environments" (a.k.a. configuration) in one file. Most
|
67
|
+
configuration files will have "development" and "production" envioronments.
|
68
|
+
There must be one section for each SQL Server or SSRS instance. Each section
|
69
|
+
is named "<key>_<environment>" where the key for SSRS servers is "ssrs". The
|
70
|
+
key for SQL Server instances must correspond to the key specified in the
|
71
|
+
invocation of "SSRS::Config.define_datasource(<name>,<key>)" above.
|
72
|
+
|
73
|
+
The SSRS server must specify the report_target and prefix keys. The SQL Server
|
74
|
+
instances must specify the database and host keys and may optionally specify
|
75
|
+
the username, password and isntance keys. If username and password are not
|
76
|
+
specified then integrated security is used.
|
77
|
+
|
78
|
+
Here is an example configuration file:
|
79
|
+
|
80
|
+
ssrs_development:
|
81
|
+
report_target: http://ssrs-dev.example.com/SSRS01_WS
|
82
|
+
prefix: /Auto/PD42/DEV
|
83
|
+
|
84
|
+
central_development:
|
85
|
+
database: PD42_IRIS_CENTRAL_DEV
|
86
|
+
username: MyUsername
|
87
|
+
password: MyPassword
|
88
|
+
host: sqlserver-dev.example.com
|
89
|
+
instance: myinstance
|
90
|
+
|
91
|
+
ssrs_production:
|
92
|
+
report_target: http://ssrs.example.com/SSRS01_WS
|
93
|
+
prefix: /Auto
|
94
|
+
|
95
|
+
central_production:
|
96
|
+
database: IRIS_CENTRAL
|
97
|
+
username: MyUsername
|
98
|
+
password: MyPassword
|
99
|
+
host: sqlserver.example.com
|
100
|
+
instance: myinstance
|
101
|
+
|
102
|
+
== Credit
|
103
|
+
|
104
|
+
The gem was initially developed as by StockSoftware for use in the Department
|
105
|
+
of Sustainability and Environment, Victoria, Australia.
|
data/lib/rptman.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
require "#{File.dirname(__FILE__)}/ssrs/ssrs-api.jar"
|
6
|
+
require "#{File.dirname(__FILE__)}/ssrs/UUID.rb"
|
7
|
+
require "#{File.dirname(__FILE__)}/ssrs/config.rb"
|
8
|
+
require "#{File.dirname(__FILE__)}/ssrs/ssrs.rb"
|
9
|
+
require "#{File.dirname(__FILE__)}/ssrs/datasource.rb"
|
10
|
+
require "#{File.dirname(__FILE__)}/ssrs/report_project.rb"
|
11
|
+
require "#{File.dirname(__FILE__)}/ssrs/report.rb"
|
12
|
+
require "#{File.dirname(__FILE__)}/ssrs/uploader.rb"
|
13
|
+
require "#{File.dirname(__FILE__)}/ssrs/bids.rb"
|
14
|
+
require "#{File.dirname(__FILE__)}/ssrs/shell.rb"
|
data/lib/ssrs/UUID.rb
ADDED
@@ -0,0 +1,326 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright(c) 2005 URABE, Shyouhei.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this code, to deal in the code without restriction, including without
|
6
|
+
# limitation the rights to use, copy, modify, merge, publish, distribute,
|
7
|
+
# sublicense, and/or sell copies of the code, and to permit persons to whom the
|
8
|
+
# code is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be
|
11
|
+
# included in all copies or substantial portions of the code.
|
12
|
+
#
|
13
|
+
# THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE CODE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
# CODE.
|
20
|
+
|
21
|
+
%w[
|
22
|
+
digest/md5
|
23
|
+
digest/sha1
|
24
|
+
socket
|
25
|
+
tmpdir
|
26
|
+
].each do |f|
|
27
|
+
require f
|
28
|
+
end
|
29
|
+
|
30
|
+
module SSRS
|
31
|
+
# Pure ruby UUID generator, which is compatible with RFC4122
|
32
|
+
UUID = Struct.new "UUID", :raw_bytes
|
33
|
+
class UUID
|
34
|
+
private_class_method :new
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def mask str # :nodoc
|
38
|
+
v = str[7]
|
39
|
+
v = v & 0b00001111
|
40
|
+
v = v | 0b01010000
|
41
|
+
str[7] = v
|
42
|
+
r = str[8]
|
43
|
+
r = r & 0b00111111
|
44
|
+
r = r | 0b10000000
|
45
|
+
str[8] = r
|
46
|
+
str
|
47
|
+
end
|
48
|
+
private :mask
|
49
|
+
|
50
|
+
# UUID generation using SHA1. Recommended over create_md5.
|
51
|
+
# Namespace object is another UUID, some of them are pre-defined below.
|
52
|
+
def create_sha1 str, namespace
|
53
|
+
sha1 = Digest::SHA1.new
|
54
|
+
sha1.update namespace.raw_bytes
|
55
|
+
sha1.update str
|
56
|
+
sum = sha1.digest
|
57
|
+
raw = mask sum[0..15]
|
58
|
+
ret = new raw
|
59
|
+
ret.raw_bytes.freeze
|
60
|
+
ret.freeze
|
61
|
+
ret
|
62
|
+
end
|
63
|
+
|
64
|
+
# UUID generation using MD5 (for backward compat.)
|
65
|
+
def create_md5 str, namespace
|
66
|
+
md5 = Digest::MD5.new
|
67
|
+
md5.update namespace.raw_bytes
|
68
|
+
md5.update str
|
69
|
+
sum = md5.digest
|
70
|
+
raw = mask sum[0..16]
|
71
|
+
ret = new raw
|
72
|
+
ret.raw_bytes.freeze
|
73
|
+
ret.freeze
|
74
|
+
ret
|
75
|
+
end
|
76
|
+
|
77
|
+
# UUID generation using random-number generator. From it's random
|
78
|
+
# nature, there's no warranty that the created ID is really universaly
|
79
|
+
# unique.
|
80
|
+
def create_random
|
81
|
+
rnd = [
|
82
|
+
rand(0x100000000),
|
83
|
+
rand(0x100000000),
|
84
|
+
rand(0x100000000),
|
85
|
+
rand(0x100000000),
|
86
|
+
].pack "N4"
|
87
|
+
raw = mask rnd
|
88
|
+
ret = new raw
|
89
|
+
ret.raw_bytes.freeze
|
90
|
+
ret.freeze
|
91
|
+
ret
|
92
|
+
end
|
93
|
+
|
94
|
+
def read_state fp # :nodoc:
|
95
|
+
fp.rewind
|
96
|
+
Marshal.load fp.read
|
97
|
+
end
|
98
|
+
|
99
|
+
def write_state fp, c, m # :nodoc:
|
100
|
+
fp.rewind
|
101
|
+
str = Marshal.dump [c, m]
|
102
|
+
fp.write str
|
103
|
+
end
|
104
|
+
|
105
|
+
private :read_state, :write_state
|
106
|
+
STATE_FILE = 'ruby-uuid'
|
107
|
+
|
108
|
+
# create the "version 1" UUID with current system clock, current UTC
|
109
|
+
# timestamp, and the IEEE 802 address (so-called MAC address).
|
110
|
+
#
|
111
|
+
# Speed notice: it's slow. It writes some data into hard drive on every
|
112
|
+
# invokation. If you want to speed this up, try remounting tmpdir with a
|
113
|
+
# memory based filesystem (such as tmpfs). STILL slow? then no way but
|
114
|
+
# rewrite it with c :)
|
115
|
+
def create( clock=nil, time=nil, mac_addr=nil )
|
116
|
+
c = t = m = nil
|
117
|
+
Dir.chdir Dir.tmpdir do
|
118
|
+
unless FileTest.exist? STATE_FILE then
|
119
|
+
# Generate a pseudo MAC address because we have no pure-ruby way
|
120
|
+
# to know the MAC address of the NIC this system uses. Note
|
121
|
+
# that cheating with pseudo arresses here is completely legal:
|
122
|
+
# see Section 4.5 of RFC4122 for details.
|
123
|
+
sha1 = Digest::SHA1.new
|
124
|
+
256.times do
|
125
|
+
r = [rand(0x100000000)].pack "N"
|
126
|
+
sha1.update r
|
127
|
+
end
|
128
|
+
str = sha1.digest
|
129
|
+
r = rand 34 # 40-6
|
130
|
+
node = str[r, 6] || str
|
131
|
+
node[0] |= 0x01 # multicast bit
|
132
|
+
k = rand 0x40000
|
133
|
+
open STATE_FILE, 'wb' do |fp|
|
134
|
+
fp.flock IO::LOCK_EX
|
135
|
+
write_state fp, k, node
|
136
|
+
fp.chmod 0o777 # must be world writable
|
137
|
+
end
|
138
|
+
end
|
139
|
+
open STATE_FILE, 'r+b' do |fp|
|
140
|
+
fp.flock IO::LOCK_EX
|
141
|
+
c, m = read_state fp
|
142
|
+
c = clock % 0x4000 if clock
|
143
|
+
m = mac_addr if mac_addr
|
144
|
+
t = time
|
145
|
+
if t.nil? then
|
146
|
+
# UUID epoch is 1582/Oct/15
|
147
|
+
tt = Time.now
|
148
|
+
t = tt.to_i*10000000 + tt.tv_usec*10 + 0x01B21DD213814000
|
149
|
+
end
|
150
|
+
c = c.succ # important; increment here
|
151
|
+
write_state fp, c, m
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
tl = t & 0xFFFF_FFFF
|
156
|
+
tm = t >> 32
|
157
|
+
tm = tm & 0xFFFF
|
158
|
+
th = t >> 48
|
159
|
+
th = th & 0x0FFF
|
160
|
+
th = th | 0x1000
|
161
|
+
cl = c & 0xFF
|
162
|
+
ch = c & 0x3F00
|
163
|
+
ch = ch >> 8
|
164
|
+
ch = ch | 0x80
|
165
|
+
pack tl, tm, th, cl, ch, m
|
166
|
+
end
|
167
|
+
|
168
|
+
# A simple GUID parser: just ignores unknown characters and convert
|
169
|
+
# hexadecimal dump into 16-octet object.
|
170
|
+
def parse obj
|
171
|
+
str = obj.to_s.sub %r/\Aurn:uuid:/, ''
|
172
|
+
str.gsub! %r/[^0-9A-Fa-f]/, ''
|
173
|
+
raw = str[0..31].to_a.pack 'H*'
|
174
|
+
ret = new raw
|
175
|
+
ret.raw_bytes.freeze
|
176
|
+
ret.freeze
|
177
|
+
ret
|
178
|
+
end
|
179
|
+
|
180
|
+
# The 'primitive constructor' of this class
|
181
|
+
# Note UUID.pack(uuid.unpack) == uuid
|
182
|
+
def pack tl, tm, th, ch, cl, n
|
183
|
+
raw = [tl, tm, th, ch, cl, n].pack "NnnCCa6"
|
184
|
+
ret = new raw
|
185
|
+
ret.raw_bytes.freeze
|
186
|
+
ret.freeze
|
187
|
+
ret
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# The 'primitive deconstructor', or the dual to pack.
|
192
|
+
# Note UUID.pack(uuid.unpack) == uuid
|
193
|
+
def unpack
|
194
|
+
raw_bytes.unpack "NnnCCa6"
|
195
|
+
end
|
196
|
+
|
197
|
+
# Generate the string representation (a.k.a GUID) of this UUID
|
198
|
+
def to_s
|
199
|
+
a = unpack
|
200
|
+
tmp = a[-1].unpack 'C*'
|
201
|
+
a[-1] = sprintf '%02x%02x%02x%02x%02x%02x', *tmp
|
202
|
+
"%08x-%04x-%04x-%02x%02x-%s" % a
|
203
|
+
end
|
204
|
+
alias guid to_s
|
205
|
+
|
206
|
+
# Convert into a RFC4122-comforming URN representation
|
207
|
+
def to_uri
|
208
|
+
"urn:uuid:" + self.to_s
|
209
|
+
end
|
210
|
+
alias urn to_uri
|
211
|
+
|
212
|
+
# Convert into 128-bit unsigned integer
|
213
|
+
# Typically a Bignum instance, but can be a Fixnum.
|
214
|
+
def to_int
|
215
|
+
tmp = self.raw_bytes.unpack "C*"
|
216
|
+
tmp.inject do |r, i|
|
217
|
+
r * 256 | i
|
218
|
+
end
|
219
|
+
end
|
220
|
+
alias to_i to_int
|
221
|
+
|
222
|
+
# Two UUIDs are said to be equal if and only if their (byte-order
|
223
|
+
# canonicalized) integer representations are equivallent. Refer RFC4122 for
|
224
|
+
# details.
|
225
|
+
def == other
|
226
|
+
to_i == other.to_i
|
227
|
+
end
|
228
|
+
|
229
|
+
include Comparable
|
230
|
+
# UUIDs are comparable (don't know what benefits are there, though).
|
231
|
+
def <=> other
|
232
|
+
to_s <=> other.to_s
|
233
|
+
end
|
234
|
+
|
235
|
+
# Pre-defined UUID Namespaces described in RFC4122 Appendix C.
|
236
|
+
NameSpace_DNS = parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
237
|
+
NameSpace_URL = parse "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
|
238
|
+
NameSpace_OID = parse "6ba7b812-9dad-11d1-80b4-00c04fd430c8"
|
239
|
+
NameSpace_X500 = parse "6ba7b814-9dad-11d1-80b4-00c04fd430c8"
|
240
|
+
|
241
|
+
# The Nil UUID in RFC4122 Section 4.1.7
|
242
|
+
Nil = parse "00000000-0000-0000-0000-000000000000"
|
243
|
+
end
|
244
|
+
|
245
|
+
if __FILE__ == $0 then
|
246
|
+
require 'test/unit'
|
247
|
+
|
248
|
+
class TC_UUID < Test::Unit::TestCase
|
249
|
+
def test_v1
|
250
|
+
u1 = UUID.create
|
251
|
+
u2 = UUID.create
|
252
|
+
assert_not_equal u1, u2
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_v1_repeatability
|
256
|
+
u1 = UUID.create 1, 2, "345678"
|
257
|
+
u2 = UUID.create 1, 2, "345678"
|
258
|
+
assert_equal u1, u2
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_v3
|
262
|
+
u1 = UUID.create_md5 "foo", UUID::NameSpace_DNS
|
263
|
+
u2 = UUID.create_md5 "foo", UUID::NameSpace_DNS
|
264
|
+
u3 = UUID.create_md5 "foo", UUID::NameSpace_URL
|
265
|
+
assert_equal u1, u2
|
266
|
+
assert_not_equal u1, u3
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_v5
|
270
|
+
u1 = UUID.create_sha1 "foo", UUID::NameSpace_DNS
|
271
|
+
u2 = UUID.create_sha1 "foo", UUID::NameSpace_DNS
|
272
|
+
u3 = UUID.create_sha1 "foo", UUID::NameSpace_URL
|
273
|
+
assert_equal u1, u2
|
274
|
+
assert_not_equal u1, u3
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_v4
|
278
|
+
# This test is not perfect, because the random nature of version 4
|
279
|
+
# UUID it is not always true that the three objects below really
|
280
|
+
# differ. But in real life it's enough to say we're OK when this
|
281
|
+
# passes.
|
282
|
+
u1 = UUID.create_random
|
283
|
+
u2 = UUID.create_random
|
284
|
+
u3 = UUID.create_random
|
285
|
+
assert_not_equal u1.raw_bytes, u2.raw_bytes
|
286
|
+
assert_not_equal u1.raw_bytes, u3.raw_bytes
|
287
|
+
assert_not_equal u2.raw_bytes, u3.raw_bytes
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_pack
|
291
|
+
u1 = UUID.pack 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4,
|
292
|
+
"\000\300O\3240\310"
|
293
|
+
assert_equal UUID::NameSpace_DNS, u1
|
294
|
+
end
|
295
|
+
|
296
|
+
def test_unpack
|
297
|
+
tl, tm, th, cl, ch, m = UUID::NameSpace_DNS.unpack
|
298
|
+
assert_equal 0x6ba7b810, tl
|
299
|
+
assert_equal 0x9dad, tm
|
300
|
+
assert_equal 0x11d1, th
|
301
|
+
assert_equal 0x80, cl
|
302
|
+
assert_equal 0xb4, ch
|
303
|
+
assert_equal "\000\300O\3240\310", m
|
304
|
+
end
|
305
|
+
|
306
|
+
def test_parse
|
307
|
+
u1 = UUID.pack 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4,
|
308
|
+
"\000\300O\3240\310"
|
309
|
+
u2 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
310
|
+
u3 = UUID.parse "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
311
|
+
assert_equal u1, u2
|
312
|
+
assert_equal u1, u3
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_to_s
|
316
|
+
u1 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
317
|
+
assert_equal "6ba7b810-9dad-11d1-80b4-00c04fd430c8", u1.to_s
|
318
|
+
end
|
319
|
+
|
320
|
+
def test_to_i
|
321
|
+
u1 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
322
|
+
assert_equal 0x6ba7b8109dad11d180b400c04fd430c8, u1.to_i
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
data/lib/ssrs/bids.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module SSRS
|
2
|
+
# Generator for "SQL Server Business Intelligence Development Studio" projects
|
3
|
+
class BIDS
|
4
|
+
def self.generate
|
5
|
+
self.generate_data_sources
|
6
|
+
self.generate_project_files
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def self.generate_project_files
|
12
|
+
SSRS::Config.upload_dirs.each do |upload_dir|
|
13
|
+
SSRS.info("Generating Project for #{upload_dir}")
|
14
|
+
actual_dir = File.expand_path("#{SSRS::Config.reports_dir}/#{upload_dir}")
|
15
|
+
filename = File.expand_path("#{SSRS::Config.projects_dir}/#{upload_dir[1,upload_dir.size].gsub('/', '_')}.rptproj")
|
16
|
+
project = SSRS::ReportProject.new(upload_dir, filename, actual_dir)
|
17
|
+
File.open(filename, 'w') do |f|
|
18
|
+
project.write(f)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.generate_data_sources
|
24
|
+
FileUtils.rm_rf SSRS::Config.projects_dir
|
25
|
+
FileUtils.mkdir_p SSRS::Config.projects_dir
|
26
|
+
SSRS::Config.datasources.each do |ds|
|
27
|
+
filename = "#{SSRS::Config.projects_dir}/#{ds.name}.rds"
|
28
|
+
SSRS.info("Generating DataSource #{ds.name}")
|
29
|
+
File.open(filename, 'w') do |f|
|
30
|
+
ds.write(f)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/ssrs/config.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
module SSRS
|
2
|
+
class Config
|
3
|
+
class Server
|
4
|
+
attr_reader :report_target
|
5
|
+
attr_reader :upload_prefix
|
6
|
+
|
7
|
+
def initialize(report_target, upload_prefix)
|
8
|
+
@report_target = report_target
|
9
|
+
@upload_prefix = upload_prefix
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_writer :environment
|
15
|
+
|
16
|
+
def environment
|
17
|
+
return 'development' unless @environment
|
18
|
+
@environment
|
19
|
+
end
|
20
|
+
|
21
|
+
# config_file is where the yaml config file is located
|
22
|
+
attr_writer :config_filename
|
23
|
+
|
24
|
+
def config_filename
|
25
|
+
raise "config_filename not specified" unless @config_filename
|
26
|
+
@config_filename
|
27
|
+
end
|
28
|
+
|
29
|
+
# reports_dir is where the report hierarchy is located
|
30
|
+
attr_writer :reports_dir
|
31
|
+
|
32
|
+
def reports_dir
|
33
|
+
raise "reports_dir not specified" unless @reports_dir
|
34
|
+
@reports_dir
|
35
|
+
end
|
36
|
+
|
37
|
+
# projects_dir is where the VS projects are generated
|
38
|
+
attr_writer :projects_dir
|
39
|
+
|
40
|
+
def projects_dir
|
41
|
+
return "#{self.reports_dir}/projects" unless @projects_dir
|
42
|
+
return @projects_dir
|
43
|
+
end
|
44
|
+
|
45
|
+
def datasources
|
46
|
+
datasources_map.values
|
47
|
+
end
|
48
|
+
|
49
|
+
def define_datasource(name, database_key)
|
50
|
+
data_source = SSRS::DataSource.new(name)
|
51
|
+
configure_datasource(data_source, database_key)
|
52
|
+
datasources_map[name] = data_source
|
53
|
+
end
|
54
|
+
|
55
|
+
def reports
|
56
|
+
unless @reports
|
57
|
+
@reports = Dir.glob("#{self.reports_dir}/**/*.rdl").collect do |filename|
|
58
|
+
SSRS::Report.new(upload_path(filename), filename)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
return @reports
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return list of dirs uploaded
|
65
|
+
def upload_dirs
|
66
|
+
self.reports.collect {|report| File.dirname(report.name)}.sort.uniq
|
67
|
+
end
|
68
|
+
|
69
|
+
def upload_prefix
|
70
|
+
current_ssrs_config.upload_prefix
|
71
|
+
end
|
72
|
+
|
73
|
+
def wsdl_path
|
74
|
+
"#{report_target}/ReportService2005.asmx"
|
75
|
+
end
|
76
|
+
|
77
|
+
def report_target
|
78
|
+
current_ssrs_config.report_target
|
79
|
+
end
|
80
|
+
|
81
|
+
def server_config(env_key)
|
82
|
+
load_ssrs_config(env_key)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def upload_path(filename)
|
88
|
+
symbolic_path = filename.gsub(Regexp.escape(reports_dir), '')
|
89
|
+
return "#{File.dirname(symbolic_path)}/#{File.basename(symbolic_path,'.rdl')}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def current_ssrs_config
|
93
|
+
@server ||= load_ssrs_config(environment)
|
94
|
+
end
|
95
|
+
|
96
|
+
def load_ssrs_config(env_key)
|
97
|
+
config_key = "ssrs_#{env_key}"
|
98
|
+
config = config_for_key(config_key)
|
99
|
+
report_target = expect_config_element(config_key, config, 'report_target').to_s
|
100
|
+
upload_prefix = expect_config_element(config_key, config, 'prefix').to_s
|
101
|
+
SSRS::Config::Server.new(report_target, upload_prefix)
|
102
|
+
end
|
103
|
+
|
104
|
+
def configure_datasource(data_source, database_key)
|
105
|
+
config_key = "#{database_key}_#{environment}"
|
106
|
+
config = config_for_key(config_key)
|
107
|
+
|
108
|
+
data_source.host = expect_config_element(config_key, config, 'host')
|
109
|
+
data_source.database = expect_config_element(config_key, config, 'database')
|
110
|
+
data_source.instance = config['instance']
|
111
|
+
data_source.username = config['username']
|
112
|
+
data_source.password = config['password']
|
113
|
+
end
|
114
|
+
|
115
|
+
def expect_config_element(config_key, config, element_key)
|
116
|
+
raise "Missing #{element_key} for #{config_key} database config" unless config[element_key]
|
117
|
+
config[element_key]
|
118
|
+
end
|
119
|
+
|
120
|
+
def config_for_key(config_key)
|
121
|
+
c = config_data[config_key]
|
122
|
+
raise "Missing configuration #{config_key} in #{self.config_filename}" unless c
|
123
|
+
c
|
124
|
+
end
|
125
|
+
|
126
|
+
def config_data
|
127
|
+
unless @config_data
|
128
|
+
raise "Unable to locate config file #{self.config_filename}" unless File.exist?(self.config_filename)
|
129
|
+
@config_data = ::YAML::load(ERB.new(IO.read(self.config_filename)).result)
|
130
|
+
end
|
131
|
+
@config_data
|
132
|
+
end
|
133
|
+
|
134
|
+
def datasources_map
|
135
|
+
@datasources ||= {}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module SSRS
|
2
|
+
class DataSource
|
3
|
+
BASE_PATH = 'DataSources'
|
4
|
+
attr_accessor :name, :host, :instance, :database, :datasource_id, :username, :password
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
self.name = name
|
8
|
+
self.datasource_id = SSRS::UUID.create.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def host_spec
|
12
|
+
"#{self.host}#{self.instance.nil? ? '' : '\\'}#{self.instance}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def symbolic_name
|
16
|
+
"#{DataSource::BASE_PATH}/#{self.name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def connection_string
|
20
|
+
auth_details = unless ( self.username || self.password )
|
21
|
+
'Integrated Security=SSPI'
|
22
|
+
else
|
23
|
+
"User Id=#{self.username};Password=#{self.password}"
|
24
|
+
end
|
25
|
+
"Data Source=#{self.host_spec};Initial Catalog=#{self.database};#{auth_details};"
|
26
|
+
end
|
27
|
+
|
28
|
+
def write(file)
|
29
|
+
file.write <<XML
|
30
|
+
<RptDataSource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
31
|
+
<Name>#{self.name}</Name>
|
32
|
+
<ConnectionProperties>
|
33
|
+
<Extension>SQL</Extension>
|
34
|
+
<ConnectString>#{connection_string}</ConnectString>
|
35
|
+
</ConnectionProperties>
|
36
|
+
<DataSourceID>#{self.datasource_id}</DataSourceID>
|
37
|
+
</RptDataSource>
|
38
|
+
XML
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/ssrs/report.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module SSRS
|
2
|
+
class Report
|
3
|
+
attr_reader :name
|
4
|
+
attr_reader :filename
|
5
|
+
|
6
|
+
def initialize(name, filename)
|
7
|
+
@name, @filename = name, filename
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_upload_version
|
11
|
+
require 'tempfile'
|
12
|
+
file = Tempfile.new("ssrs_report")
|
13
|
+
xformed_document.write file
|
14
|
+
xformed_filename = file.path
|
15
|
+
file.close
|
16
|
+
xformed_filename
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def xformed_document
|
22
|
+
document = self.document
|
23
|
+
REXML::XPath.each(document.root, "//Report/DataSources/DataSource/DataSourceReference") do |element|
|
24
|
+
text_node = element.get_text
|
25
|
+
text_node.value = "#{SSRS::Config.upload_prefix}/#{DataSource::BASE_PATH}/#{text_node.value}"
|
26
|
+
end
|
27
|
+
document
|
28
|
+
end
|
29
|
+
|
30
|
+
def document
|
31
|
+
require 'rexml/document'
|
32
|
+
REXML::Document.new(File.read(self.filename))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module SSRS
|
2
|
+
class ReportProject
|
3
|
+
attr_reader :name
|
4
|
+
attr_accessor :project_filename
|
5
|
+
attr_accessor :dir
|
6
|
+
|
7
|
+
def initialize(name, project_filename, dir)
|
8
|
+
@name, @project_filename, @dir = name, project_filename, dir
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(file)
|
12
|
+
file.write <<XML
|
13
|
+
<?xml version="1.0" encoding="utf-8"?>
|
14
|
+
<Project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
15
|
+
<State>$base64$PFNvdXJjZUNvbnRyb2xJbmZvIHhtbG5zOnhzZD0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOmRkbDI9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDAzL2VuZ2luZS8yIiB4bWxuczpkZGwyXzI9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDAzL2VuZ2luZS8yLzIiIHhtbG5zOmRkbDEwMF8xMDA9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vYW5hbHlzaXNzZXJ2aWNlcy8yMDA4L2VuZ2luZS8xMDAvMTAwIiB4bWxuczpkd2Q9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vRGF0YVdhcmVob3VzZS9EZXNpZ25lci8xLjAiPg0KICA8RW5hYmxlZD5mYWxzZTwvRW5hYmxlZD4NCiAgPFByb2plY3ROYW1lPjwvUHJvamVjdE5hbWU+DQogIDxBdXhQYXRoPjwvQXV4UGF0aD4NCiAgPExvY2FsUGF0aD48L0xvY2FsUGF0aD4NCiAgPFByb3ZpZGVyPjwvUHJvdmlkZXI+DQo8L1NvdXJjZUNvbnRyb2xJbmZvPg==</State>
|
16
|
+
<DataSources>
|
17
|
+
XML
|
18
|
+
SSRS::Config.datasources.each do |ds|
|
19
|
+
file.write <<XML
|
20
|
+
<ProjectItem>
|
21
|
+
<Name>#{ds.name}.rds</Name>
|
22
|
+
<FullPath>#{ds.name}.rds</FullPath>
|
23
|
+
</ProjectItem>
|
24
|
+
XML
|
25
|
+
end
|
26
|
+
file.write <<XML
|
27
|
+
</DataSources>
|
28
|
+
<Reports>
|
29
|
+
XML
|
30
|
+
|
31
|
+
Dir["#{self.dir}/*.rdl"].each do |f|
|
32
|
+
file.write <<XML
|
33
|
+
<ProjectItem>
|
34
|
+
<Name>#{File.basename(f)}</Name>
|
35
|
+
<FullPath>#{relativepath(File.expand_path(f), project_filename)}</FullPath>
|
36
|
+
</ProjectItem>
|
37
|
+
XML
|
38
|
+
end
|
39
|
+
|
40
|
+
file.write <<XML
|
41
|
+
</Reports>
|
42
|
+
<Configurations>
|
43
|
+
XML
|
44
|
+
development = SSRS::Config.server_config("development")
|
45
|
+
file.write gen_configuration("Debug", development)
|
46
|
+
file.write gen_configuration("DebugLocal", development)
|
47
|
+
production = SSRS::Config.server_config("production") rescue nil
|
48
|
+
file.write gen_configuration("Release", production) if production
|
49
|
+
file.write <<XML
|
50
|
+
</Configurations>
|
51
|
+
</Project>
|
52
|
+
XML
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def gen_configuration(name, server_config)
|
58
|
+
<<XML
|
59
|
+
<Configuration>
|
60
|
+
<Name>#{name}</Name>
|
61
|
+
<Platform>Win32</Platform>
|
62
|
+
<Options>
|
63
|
+
<TargetServerURL>#{server_config.report_target}</TargetServerURL>
|
64
|
+
<TargetFolder>#{server_config.upload_prefix}#{self.name}</TargetFolder>
|
65
|
+
<TargetDataSourceFolder>#{server_config.upload_prefix}/#{DataSource::BASE_PATH}</TargetDataSourceFolder>
|
66
|
+
</Options>
|
67
|
+
</Configuration>
|
68
|
+
XML
|
69
|
+
end
|
70
|
+
|
71
|
+
# Convert the given absolute path into a path
|
72
|
+
# relative to the second given absolute path.
|
73
|
+
def relativepath(abspath, relativeto)
|
74
|
+
path = abspath.split(File::SEPARATOR)
|
75
|
+
rel = relativeto.split(File::SEPARATOR)
|
76
|
+
while (path.length > 0) && (path.first == rel.first)
|
77
|
+
path.shift
|
78
|
+
rel.shift
|
79
|
+
end
|
80
|
+
('..' + File::SEPARATOR) * (rel.length - 1) + path.join(File::SEPARATOR)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/ssrs/shell.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module SSRS
|
2
|
+
# Generator for "SQL Server Business Intelligence Development Studio" projects
|
3
|
+
class Shell
|
4
|
+
class << self
|
5
|
+
attr_writer :generate_projects
|
6
|
+
|
7
|
+
def generate_projects?
|
8
|
+
@generate_projects ||= false
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_writer :upload_reports
|
12
|
+
|
13
|
+
def upload_reports?
|
14
|
+
@upload_reports ||= false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.run
|
19
|
+
parse_args
|
20
|
+
banner
|
21
|
+
SSRS::BIDS.generate if generate_projects?
|
22
|
+
SSRS::Uploader.upload if upload_reports?
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.banner
|
28
|
+
SSRS.info("Rptman:")
|
29
|
+
SSRS.info("\tEnvironment: #{SSRS::Config.environment}")
|
30
|
+
SSRS.info("\tDataSource Count: #{SSRS::Config.datasources.size}")
|
31
|
+
SSRS.info("\tReport Count: #{SSRS::Config.reports.size}")
|
32
|
+
SSRS.info("\tReport Dir Count: #{SSRS::Config.upload_dirs.size}")
|
33
|
+
SSRS.info("\tUpload Prefix: #{SSRS::Config.upload_prefix}")
|
34
|
+
SSRS.info("\tReport Target: #{SSRS::Config.report_target}")
|
35
|
+
SSRS.info("")
|
36
|
+
if !generate_projects? && !upload_reports?
|
37
|
+
SSRS.info("Run with -h for help")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.parse_args
|
42
|
+
SSRS::Config.environment = "development"
|
43
|
+
|
44
|
+
optparse = OptionParser.new do |opts|
|
45
|
+
opts.on('-v', '--verbose', 'Output more information') do
|
46
|
+
Java::IrisSSRS::SSRS.setupLogger(true)
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on('-e', '--environment environment', 'Database environment to use') do |environment|
|
50
|
+
SSRS::Config.environment = environment
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on('-u', '--upload', 'Upload the reports') do
|
54
|
+
SSRS::Shell.upload_reports = true
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on('-p', '--generate-projects', 'Generate the Buisness Intelligence Studio projects') do
|
58
|
+
SSRS::Shell.generate_projects = true
|
59
|
+
end
|
60
|
+
|
61
|
+
# This displays the help screen, all programs are
|
62
|
+
# assumed to have this option.
|
63
|
+
opts.on('-h', '--help', 'Display this screen') do
|
64
|
+
puts opts
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Parse the command-line. Remember there are two forms
|
70
|
+
# of the parse method. The 'parse' method simply parses
|
71
|
+
# ARGV, while the 'parse!' method parses ARGV and removes
|
72
|
+
# any options found there, as well as any parameters for
|
73
|
+
# the options. What's left is the list of files to resize.
|
74
|
+
optparse.parse!
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
Binary file
|
data/lib/ssrs/ssrs.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module SSRS
|
2
|
+
class Uploader
|
3
|
+
def self.upload
|
4
|
+
ssrs_soap_port = Java::IrisSSRS::SSRS.new(Java::JavaNet.URL.new(SSRS::Config.wsdl_path),
|
5
|
+
SSRS::Config.upload_prefix)
|
6
|
+
self.upload_datasources(ssrs_soap_port)
|
7
|
+
self.upload_reports(ssrs_soap_port)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def self.upload_datasources(ssrs_soap_port)
|
13
|
+
ssrs_soap_port.mkdir(SSRS::DataSource::BASE_PATH)
|
14
|
+
SSRS::Config.datasources.each do |ds|
|
15
|
+
ssrs_soap_port.delete(ds.symbolic_name)
|
16
|
+
ssrs_soap_port.createSQLDataSource(ds.symbolic_name, ds.connection_string)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.upload_reports(ssrs_soap_port)
|
21
|
+
top_level_upload_dirs =
|
22
|
+
SSRS::Config.upload_dirs.collect { |d| d.split('/').delete_if { |p| p == "" }.first }.sort.uniq
|
23
|
+
top_level_upload_dirs.each do |upload_dir|
|
24
|
+
ssrs_soap_port.delete(upload_dir)
|
25
|
+
end
|
26
|
+
SSRS::Config.reports.each do |report|
|
27
|
+
ssrs_soap_port.mkdir(File.dirname(report.name))
|
28
|
+
ssrs_soap_port.createReport(report.name, Java::JavaIo.File.new(report.generate_upload_version))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
namespace :rptman do
|
2
|
+
namespace :vs_projects do
|
3
|
+
desc "Generate MS VS projects for each report dir"
|
4
|
+
task :generate do
|
5
|
+
SSRS::BIDS.generate
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Clean generated projects"
|
9
|
+
task :clean do
|
10
|
+
rm_rf SSRS::Config.projects_dir
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
namespace :ssrs do
|
15
|
+
desc "Upload reports to SSRS server"
|
16
|
+
task :upload do
|
17
|
+
SSRS::Uploader.upload
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/rptman.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'rptman'
|
3
|
+
spec.version = `git describe`.strip.split('-').first
|
4
|
+
spec.authors = ['Peter Donald']
|
5
|
+
spec.email = ["peter@realityforge.org"]
|
6
|
+
|
7
|
+
spec.homepage = "http://github.com/stocksoftware/rptman"
|
8
|
+
spec.summary = "Tool for managing SSRS reports"
|
9
|
+
spec.description = <<-TEXT
|
10
|
+
This is a command line tool and suite of rake tasks for uploading SSRS
|
11
|
+
reports to a server. The tool can also generate project files for
|
12
|
+
the "SQL Server Business Intelligence Development Studio".
|
13
|
+
TEXT
|
14
|
+
spec.files = Dir['{lib}/**/*', '*.gemspec'] +
|
15
|
+
['lib/ssrs/ssrs-api.jar','LICENSE', 'README.rdoc', 'CHANGELOG']
|
16
|
+
spec.require_paths = ['lib']
|
17
|
+
spec.platform = RUBY_PLATFORM[/java/]
|
18
|
+
|
19
|
+
spec.has_rdoc = false
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rptman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Donald
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-06-26 00:00:00 +10:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: |
|
17
|
+
This is a command line tool and suite of rake tasks for uploading SSRS
|
18
|
+
reports to a server. The tool can also generate project files for
|
19
|
+
the "SQL Server Business Intelligence Development Studio".
|
20
|
+
|
21
|
+
email:
|
22
|
+
- peter@realityforge.org
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- lib/ssrs/shell.rb
|
31
|
+
- lib/ssrs/UUID.rb
|
32
|
+
- lib/ssrs/uploader.rb
|
33
|
+
- lib/ssrs/report.rb
|
34
|
+
- lib/ssrs/config.rb
|
35
|
+
- lib/ssrs/bids.rb
|
36
|
+
- lib/ssrs/ssrs.rb
|
37
|
+
- lib/ssrs/datasource.rb
|
38
|
+
- lib/ssrs/report_project.rb
|
39
|
+
- lib/tasks/rptman.rake
|
40
|
+
- lib/rptman.rb
|
41
|
+
- rptman.gemspec
|
42
|
+
- lib/ssrs/ssrs-api.jar
|
43
|
+
- LICENSE
|
44
|
+
- README.rdoc
|
45
|
+
- CHANGELOG
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/stocksoftware/rptman
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.3.5
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Tool for managing SSRS reports
|
74
|
+
test_files: []
|
75
|
+
|