rptman 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|