jeni 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Bugs.rdoc +6 -0
- data/Gemfile +14 -0
- data/History.txt +9 -0
- data/Intro.txt +3 -0
- data/LICENCE.rdoc +159 -0
- data/README.md +188 -0
- data/lib/jeni.rb +374 -0
- data/lib/jeni/actions.rb +400 -0
- data/lib/jeni/errors.rb +22 -0
- data/lib/jeni/io.rb +71 -0
- data/lib/jeni/options.rb +68 -0
- data/lib/jeni/optparse.rb +84 -0
- data/lib/jeni/utils.rb +181 -0
- data/lib/jeni/version.rb +13 -0
- data/spec/jeni_spec.rb +85 -0
- data/spec/jeni_utils_spec.rb +297 -0
- data/spec/spec_helper.rb +26 -0
- data/test/examples/source/coati.haml.conf +37 -0
- data/test/examples/source/executable +3 -0
- data/test/examples/source/jenny-diff.rb +64 -0
- data/test/examples/source/jenny.rb +63 -0
- data/test/examples/source/shebang.rb +3 -0
- data/test/examples/source/subfiles/subfile_1.rb +63 -0
- data/test/examples/source/template.haml.rb +10 -0
- data/test/examples/target/archive/coati.haml.conf +37 -0
- data/test/examples/target/archive/executable +3 -0
- data/test/examples/target/archive/jenny-diff.rb +64 -0
- data/test/examples/target/archive/jenny.rb +63 -0
- data/test/examples/target/archive/shebang.rb +3 -0
- data/test/examples/target/archive/subfiles/subfile_1.rb +63 -0
- data/test/examples/target/archive/template.haml.rb +10 -0
- data/test/examples/target/jenny.rb +63 -0
- data/test/examples/target/jenny_link.rb +63 -0
- data/test/examples/target2/coati.conf +36 -0
- data/test/examples/target2/coati.haml.conf +37 -0
- data/test/examples/target2/executable +3 -0
- data/test/examples/target2/jenny-diff.rb +64 -0
- data/test/examples/target2/jenny.rb +63 -0
- data/test/examples/target2/jenny_link.rb +63 -0
- data/test/examples/target2/jenny_template.rb +10 -0
- data/test/examples/target2/jenny_test.rb +63 -0
- data/test/examples/target2/shebang.rb +3 -0
- data/test/examples/target2/std_template.rb +12 -0
- data/test/examples/target2/template.haml.rb +10 -0
- data/test/examples/test1.rb +30 -0
- data/test/examples/test2.rb +27 -0
- data/test/examples/test_args +24 -0
- data/test/examples/test_users +16 -0
- metadata +162 -0
data/Bugs.rdoc
ADDED
data/Gemfile
ADDED
data/History.txt
ADDED
data/Intro.txt
ADDED
data/LICENCE.rdoc
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
Copyright (c) 2012 Robert Sharp
|
2
|
+
|
3
|
+
This software is licensed for use under the Open Software Licence v. 3.0
|
4
|
+
The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
5
|
+
and below. Under the terms of this licence, all derivative works
|
6
|
+
must themselves be licensed under the Open Software Licence v. 3.0
|
7
|
+
|
8
|
+
Open Source Initiative OSI - The Open Software Licence 3.0:Licensing
|
9
|
+
[OSI Approved Licence]
|
10
|
+
Open Software Licence ("OSL") v. 3.0
|
11
|
+
|
12
|
+
This Open Software Licence (the "Licence") applies to any original work of authorship
|
13
|
+
(the "Original Work") whose owner (the "Licensor") has placed the following licensing
|
14
|
+
notice adjacent to the copyright notice for the Original Work:
|
15
|
+
|
16
|
+
Licensed under the Open Software Licence version 3.0
|
17
|
+
|
18
|
+
1) Grant of Copyright Licence. Licensor grants You a worldwide, royalty-free, non-exclusive,
|
19
|
+
sublicensable license, for the duration of the copyright, to do the following:
|
20
|
+
|
21
|
+
a) to reproduce the Original Work in copies, either alone or as part of a collective work;
|
22
|
+
|
23
|
+
b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby
|
24
|
+
creating derivative works ("Derivative Works") based upon the Original Work;
|
25
|
+
|
26
|
+
c) to distribute or communicate copies of the Original Work and Derivative Works to
|
27
|
+
the public, with the proviso that copies of Original Work or Derivative Works that
|
28
|
+
You distribute or communicate shall be licensed under this Open Software Licence;
|
29
|
+
|
30
|
+
d) to perform the Original Work publicly; and
|
31
|
+
|
32
|
+
e) to display the Original Work publicly.
|
33
|
+
|
34
|
+
2) Grant of Patent Licence. Licensor grants You a worldwide, royalty-free, non-exclusive,
|
35
|
+
sublicensable license, under patent claims owned or controlled by the Licensor that are
|
36
|
+
embodied in the Original Work as furnished by the Licensor, for the duration of the
|
37
|
+
patents, to make, use, sell, offer for sale, have made, and import the Original Work
|
38
|
+
and Derivative Works.
|
39
|
+
|
40
|
+
3) Grant of Source Code Licence. The term "Source Code" means the preferred form of
|
41
|
+
the Original Work for making modifications to it and all available documentation
|
42
|
+
describing how to modify the Original Work. Licensor agrees to provide a machine-readable
|
43
|
+
copy of the Source Code of the Original Work along with each copy of the Original Work
|
44
|
+
that Licensor distributes. Licensor reserves the right to satisfy this obligation by
|
45
|
+
placing a machine-readable copy of the Source Code in an information repository reasonably
|
46
|
+
calculated to permit inexpensive and convenient access by You for as long as Licensor
|
47
|
+
continues to distribute the Original Work.
|
48
|
+
|
49
|
+
4) Exclusions From Licence Grant. Neither the names of Licensor, nor the names of
|
50
|
+
any contributors to the Original Work, nor any of their trademarks or service marks,
|
51
|
+
may be used to endorse or promote products derived from this Original Work without
|
52
|
+
express prior permission of the Licensor. Except as expressly stated herein, nothing
|
53
|
+
in this Licence grants any license to Licensor's trademarks, copyrights, patents,
|
54
|
+
trade secrets or any other intellectual property. No patent license is granted to
|
55
|
+
make, use, sell, offer for sale, have made, or import embodiments of any patent claims
|
56
|
+
other than the licensed claims defined in Section 2. No license is granted to the
|
57
|
+
trademarks of Licensor even if such marks are included in the Original Work. Nothing
|
58
|
+
in this Licence shall be interpreted to prohibit Licensor from licensing under terms
|
59
|
+
different from this Licence any Original Work that Licensor otherwise would have a
|
60
|
+
right to license.
|
61
|
+
|
62
|
+
5) External Deployment. The term "External Deployment" means the use, distribution,
|
63
|
+
or communication of the Original Work or Derivative Works in any way such that the
|
64
|
+
Original Work or Derivative Works may be used by anyone other than You, whether those
|
65
|
+
works are distributed or communicated to those persons or made available as an application
|
66
|
+
intended for use over a network. As an express condition for the grants of license hereunder,
|
67
|
+
You must treat any External Deployment by You of the Original Work or a Derivative Work
|
68
|
+
as a distribution under section 1(c).
|
69
|
+
|
70
|
+
6) Attribution Rights. You must retain, in the Source Code of any Derivative Works
|
71
|
+
that You create, all copyright, patent, or trademark notices from the Source Code of
|
72
|
+
the Original Work, as well as any notices of licensing and any descriptive text identified
|
73
|
+
therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works
|
74
|
+
that You create to carry a prominent Attribution Notice reasonably calculated to inform
|
75
|
+
recipients that You have modified the Original Work.
|
76
|
+
|
77
|
+
7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright
|
78
|
+
in and to the Original Work and the patent rights granted herein by Licensor are owned by
|
79
|
+
the Licensor or are sublicensed to You under the terms of this Licence with the permission
|
80
|
+
of the contributor(s) of those copyrights and patent rights. Except as expressly stated in
|
81
|
+
the immediately preceding sentence, the Original Work is provided under this Licence on an
|
82
|
+
"AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation,
|
83
|
+
the warranties of non-infringement, merchantability or fitness for a particular purpose.
|
84
|
+
THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY
|
85
|
+
constitutes an essential part of this Licence. No license to the Original Work is granted
|
86
|
+
by this Licence except under this disclaimer.
|
87
|
+
|
88
|
+
8) Limitation of Liability. Under no circumstances and under no legal theory, whether
|
89
|
+
in tort (including negligence), contract, or otherwise, shall the Licensor be liable
|
90
|
+
to anyone for any indirect, special, incidental, or consequential damages of any character
|
91
|
+
arising as a result of this Licence or the use of the Original Work including, without
|
92
|
+
limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction,
|
93
|
+
or any and all other commercial damages or losses. This limitation of liability shall not
|
94
|
+
apply to the extent applicable law prohibits such limitation.
|
95
|
+
|
96
|
+
9) Acceptance and Termination. If, at any time, You expressly assented to this Licence,
|
97
|
+
that assent indicates your clear and irrevocable acceptance of this Licence and all of its
|
98
|
+
terms and conditions. If You distribute or communicate copies of the Original Work or a
|
99
|
+
Derivative Work, You must make a reasonable effort under the circumstances to obtain the
|
100
|
+
express assent of recipients to the terms of this Licence. This Licence conditions your
|
101
|
+
rights to undertake the activities listed in Section 1, including your right to create
|
102
|
+
Derivative Works based upon the Original Work, and doing so without honoring these
|
103
|
+
terms and conditions is prohibited by copyright law and international treaty. Nothing
|
104
|
+
in this Licence is intended to affect copyright exceptions and limitations (including
|
105
|
+
"fair use" or "fair dealing"). This Licence shall terminate immediately and You may no
|
106
|
+
longer exercise any of the rights granted to You by this Licence upon your failure to
|
107
|
+
honor the conditions in Section 1(c).
|
108
|
+
|
109
|
+
10) Termination for Patent Action. This Licence shall terminate automatically and
|
110
|
+
You may no longer exercise any of the rights granted to You by this Licence as of
|
111
|
+
the date You commence an action, including a cross-claim or counterclaim, against
|
112
|
+
Licensor or any licensee alleging that the Original Work infringes a patent. This
|
113
|
+
termination provision shall not apply for an action alleging patent infringement
|
114
|
+
by combinations of the Original Work with other software or hardware.
|
115
|
+
|
116
|
+
11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this Licence
|
117
|
+
may be brought only in the courts of a jurisdiction wherein the Licensor resides or
|
118
|
+
in which Licensor conducts its primary business, and under the laws of that jurisdiction
|
119
|
+
excluding its conflict-of-law provisions. The application of the United Nations Convention
|
120
|
+
on Contracts for the International Sale of Goods is expressly excluded. Any use of
|
121
|
+
the Original Work outside the scope of this Licence or after its termination shall be
|
122
|
+
subject to the requirements and penalties of copyright or patent law in the appropriate
|
123
|
+
jurisdiction. This section shall survive the termination of this Licence.
|
124
|
+
|
125
|
+
12) Attorneys' Fees. In any action to enforce the terms of this Licence or seeking
|
126
|
+
damages relating thereto, the prevailing party shall be entitled to recover its costs
|
127
|
+
and expenses, including, without limitation, reasonable attorneys' fees and costs
|
128
|
+
incurred in connection with such action, including any appeal of such action. This
|
129
|
+
section shall survive the termination of this Licence.
|
130
|
+
|
131
|
+
13) Miscellaneous. If any provision of this Licence is held to be unenforceable,
|
132
|
+
such provision shall be reformed only to the extent necessary to make it enforceable.
|
133
|
+
|
134
|
+
14) Definition of "You" in This Licence. "You" throughout this Licence, whether in
|
135
|
+
upper or lower case, means an individual or a legal entity exercising rights under,
|
136
|
+
and complying with all of the terms of, this Licence. For legal entities, "You" includes
|
137
|
+
any entity that controls, is controlled by, or is under common control with you. For
|
138
|
+
purposes of this definition, "control" means (i) the power, direct or indirect, to cause
|
139
|
+
the direction or management of such entity, whether by contract or otherwise, or (ii)
|
140
|
+
ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
|
141
|
+
ownership of such entity.
|
142
|
+
|
143
|
+
15) Right to Use. You may use the Original Work in all ways not otherwise restricted or
|
144
|
+
conditioned by this Licence or by law, and Licensor promises not to interfere with or be
|
145
|
+
responsible for such uses by You.
|
146
|
+
|
147
|
+
16) Modification of This Licence. This Licence is Copyright © 2005 Lawrence Rosen.
|
148
|
+
Permission is granted to copy, distribute, or communicate this Licence without modification.
|
149
|
+
Nothing in this Licence permits You to modify this Licence as applied to the Original Work
|
150
|
+
or to Derivative Works. However, You may modify the text of this Licence and copy, distribute
|
151
|
+
or communicate your modified version (the "Modified Licence") and apply it to other original
|
152
|
+
works of authorship subject to the following conditions: (i) You may not indicate in any way
|
153
|
+
that your Modified Licence is the "Open Software Licence" or "OSL" and you may not use those
|
154
|
+
names in the name of your Modified Licence; (ii) You must replace the notice specified in the
|
155
|
+
first paragraph above with the notice "Licensed under <insert your license name here>" or
|
156
|
+
with a notice of your own that is not confusingly similar to the notice in this Licence;
|
157
|
+
and (iii) You may not claim that your original works are open source software unless your
|
158
|
+
Modified Licence has been approved by Open Source Initiative (OSI) and You comply with
|
159
|
+
its license review and certification process.
|
data/README.md
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# JENI
|
2
|
+
|
3
|
+
_(a.k.a Jumpin Ermin's Nifty Installer)_
|
4
|
+
|
5
|
+
A simple alternative to rubigen and thor that can be used to create a post-install script
|
6
|
+
for a gem needing more than the standard file ops covered by rubygems. It can also be used
|
7
|
+
for straight directories instead of gems, if required.
|
8
|
+
|
9
|
+
**GitHub:** [https://github.com/osburn-sharp/jeni](https://github.com/osburn-sharp/jeni)
|
10
|
+
|
11
|
+
**RubyDoc:** [http://rdoc.info/github/osburn-sharp/jeni/frames](http://rdoc.info/github/osburn-sharp/jeni/frames)
|
12
|
+
|
13
|
+
**RubyGems:** [https://rubygems.org/gems/jeni](https://rubygems.org/gems/jeni)
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
To install,
|
18
|
+
|
19
|
+
gem install jeni.
|
20
|
+
|
21
|
+
It is a plain library with no binaries or the like. Documentation is available from [RDoc]()
|
22
|
+
|
23
|
+
## Getting Started
|
24
|
+
|
25
|
+
To use, create an executable, require jeni, create an instance of `Jeni::Installer`
|
26
|
+
using the new or new_from_gem method and a block, call whatever methods you need to install files
|
27
|
+
etc. and then `run!` the block to do the real work. See documentation for details of available methods
|
28
|
+
and options.
|
29
|
+
|
30
|
+
### Example
|
31
|
+
|
32
|
+
This is a simple example:
|
33
|
+
|
34
|
+
# start the installation block
|
35
|
+
Jeni::Installer.new_from_gem('my_gem') do |jeni|
|
36
|
+
jeni.pretend(opt[:pretend])
|
37
|
+
# copy a file
|
38
|
+
jeni.file('source.rb', '/etc/target.rb')
|
39
|
+
# create a wrapper
|
40
|
+
jeni.wrapper('sbin/jenerate.rb', '/usr/sbin/jenerate')
|
41
|
+
end.run!
|
42
|
+
# and run jeni
|
43
|
+
|
44
|
+
### Actions
|
45
|
+
|
46
|
+
Jeni has the following actions, which for a gem (new_from_gem) will look for the source relative to the gem,
|
47
|
+
and for a directory (new) will look relative to that directory:
|
48
|
+
|
49
|
+
+ *{Jeni::Installer#file file}* -
|
50
|
+
copy a file from the source to the filesystem
|
51
|
+
|
52
|
+
+ *{Jeni::Installer#directory directory}* -
|
53
|
+
copy all the files in a directory to the filesystem
|
54
|
+
|
55
|
+
+ *{Jeni::Installer#empty_directory empty_directory}* -
|
56
|
+
create an empty directory with no contents.
|
57
|
+
|
58
|
+
+ *{Jeni::Installer#template template}* -
|
59
|
+
generate a file from a template
|
60
|
+
|
61
|
+
+ *{Jeni::Installer#standard_template standard_template}* -
|
62
|
+
as for template, but looks in standard locations for the template file
|
63
|
+
|
64
|
+
+ *{Jeni::Installer#wrapper wrapper}* -
|
65
|
+
create a wrapper script that calls a gem binary/script - limited to gems only
|
66
|
+
|
67
|
+
+ *{Jeni::Installer#link link}* -
|
68
|
+
create a link to a file
|
69
|
+
|
70
|
+
+ *{Jeni::Installer#message message}* -
|
71
|
+
output a message to the user
|
72
|
+
|
73
|
+
+ *{Jeni::Installer#user user}* -
|
74
|
+
add a new user to the system
|
75
|
+
|
76
|
+
+ *{Jeni::Installer#group group}* -
|
77
|
+
add a new group to the system
|
78
|
+
|
79
|
+
+ *{Jeni::Installer#file_exists? file_exists?}* -
|
80
|
+
check that a given file exists, and optionally that it is executable
|
81
|
+
|
82
|
+
The file, directory and template methods take an options hash, which accepts the following options:
|
83
|
+
|
84
|
+
+ *:chown* - change the owner of the copied file(s)
|
85
|
+
+ *:chgrp* - change the group of the copied file(s)
|
86
|
+
+ *:chmod* - change the mode of the copied file(s) which should be given in octal (e.g. 0755 and not 755 or '755')
|
87
|
+
|
88
|
+
For example:
|
89
|
+
|
90
|
+
jeni.file('source.rb', '/etc/target.rb', :chown=>'robert')
|
91
|
+
|
92
|
+
If the source is a relative path then it will be looked for relative to the Installer.
|
93
|
+
So, for a gem {Jeni::Installer.new_from_gem} it will be relative to the gem's directory and for a directory
|
94
|
+
{Jeni::Installer.new} it will be relative to that. If the source is an absolute path (starts with '/')
|
95
|
+
then it will be used as given.
|
96
|
+
|
97
|
+
Global options can also be set, as defined in #{Jeni::Options}. The same options can be set using {Jeni::Optparse#optparse}
|
98
|
+
to automatically process command line options. Call it with ARGV instead of manually setting Jeni's options.
|
99
|
+
|
100
|
+
Jeni::Installer.new_from_gem('jeni') do |jeni|
|
101
|
+
jeni.optparse(ARGV)
|
102
|
+
jeni.file('source/jeni.rb', File.join(target_dir, 'jeni_test.rb'), :chown=>'robert')
|
103
|
+
jeni.file('source/jeni.rb', File.join(target_dir, 'jeni.rb'), :chown=>'robert')
|
104
|
+
jeni.directory('source', target_dir)
|
105
|
+
jeni.wrapper('source/executable', File.join(target_dir, 'executable'), :chmod=>true)
|
106
|
+
jeni.link('source/jeni.rb', File.join(target_dir, 'jeni_link.rb'))
|
107
|
+
end.run!
|
108
|
+
|
109
|
+
|
110
|
+
## Code Walkthrough
|
111
|
+
|
112
|
+
The main class is {Jeni::Installer} and the instance methods provide the actions that the installer can carry out.
|
113
|
+
Each method makes relevant checks (e.g. target directory is writeable) and queues one of more action requests,
|
114
|
+
e.g. to copy a file and then change the owner. The {Jeni::Installer#run!} method checks if any errors were
|
115
|
+
raised by the checks and aborts with error messages if they were. Otherwise it dispatches each action method with the
|
116
|
+
saved parameters.
|
117
|
+
|
118
|
+
The hard work is all done in {Jeni::Actions} and these methods are not intended to be directly accessible to the user.
|
119
|
+
Each action method controls messages and any interaction with the user and then carries out the intended action if
|
120
|
+
required. User IO is achieved through {Jeni::IO}. If interaction is required the user is prompted with a list of choices
|
121
|
+
and the appropriate action carried out as a result. This could include, for example, printing a diff listing between
|
122
|
+
an existing file and a new file intended to replace it.
|
123
|
+
|
124
|
+
Jeni has a variety of options that can be set and are separately defined in {Jeni::Options}. Alternatively, use
|
125
|
+
{Jeni::Optparse#optparse} mixin to set up these options from the command line (recommended).
|
126
|
+
|
127
|
+
The code is available from [GitHub](https://github.com/osburn-sharp/jeni)
|
128
|
+
|
129
|
+
## Dependencies
|
130
|
+
|
131
|
+
A ruby compiler - works with 1.8.7.
|
132
|
+
|
133
|
+
Check the {file:Gemfile} for other dependencies.
|
134
|
+
|
135
|
+
### Documentation
|
136
|
+
|
137
|
+
Documentation is best viewed using Yard. Documentation is available from [Rubydoc](http://rdoc.info/github/osburn-sharp/jeni/frames)
|
138
|
+
|
139
|
+
## Testing/Modifying
|
140
|
+
|
141
|
+
Testing can be carried out with the GitHub sources and uses rspec. There is a complete rspec test suite
|
142
|
+
for the Utils module (spec/jeni_utils_spec.rb).
|
143
|
+
|
144
|
+
There is also a manual test that is an example that shows a range of possible results for a mock gem. Run this with
|
145
|
+
|
146
|
+
$ test/examples/test1.rb
|
147
|
+
|
148
|
+
The same tests are used for a source directory instead of a gem:
|
149
|
+
|
150
|
+
$ test/examples/test2.rb
|
151
|
+
|
152
|
+
There is also a variant on the above that uses optparse and can therefore accept any of the proposed
|
153
|
+
options:
|
154
|
+
|
155
|
+
$ test/examples/test_args -p
|
156
|
+
|
157
|
+
Will pretend. For more details:
|
158
|
+
|
159
|
+
$ test/examples/test_args --help
|
160
|
+
|
161
|
+
To test users and groups there is the following, which will fail if not run as root:
|
162
|
+
|
163
|
+
$ test/examples/test_users
|
164
|
+
|
165
|
+
## Bugs
|
166
|
+
|
167
|
+
Details of any unresolved bugs and change requests are in {file:Bugs.rdoc Bugs}. Issues can be logged and tracked through
|
168
|
+
[GitHub](https://github.com/osburn-sharp/jeni/issues).
|
169
|
+
|
170
|
+
## Changelog
|
171
|
+
|
172
|
+
See {file:History.txt} for a summary change history.
|
173
|
+
|
174
|
+
## Author and Contact
|
175
|
+
|
176
|
+
The author may be contacted by via [GitHub](http://github.com/osburn-sharp)
|
177
|
+
|
178
|
+
## Copyright and Licence
|
179
|
+
|
180
|
+
Copyright (c) 2012 Robert Sharp
|
181
|
+
|
182
|
+
This software is licensed under the terms defined in {file:LICENCE.rdoc}
|
183
|
+
|
184
|
+
## Warranty
|
185
|
+
|
186
|
+
This software is provided "as is" and without any express or implied
|
187
|
+
warranties, including, without limitation, the implied warranties of
|
188
|
+
merchantibility and fitness for a particular purpose.
|
data/lib/jeni.rb
ADDED
@@ -0,0 +1,374 @@
|
|
1
|
+
#
|
2
|
+
# Author:: R.J.Sharp
|
3
|
+
# Email:: robert(a)osburn-sharp.ath.cx
|
4
|
+
# Copyright:: Copyright (c) 2012
|
5
|
+
# License:: Open Software Licence v3.0
|
6
|
+
#
|
7
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
8
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
9
|
+
# and in the file LICENCE. Under the terms of this licence, all derivative works
|
10
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
11
|
+
#
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'rubygems'
|
15
|
+
require 'jeni/utils'
|
16
|
+
require 'jeni/actions'
|
17
|
+
require 'jeni/io'
|
18
|
+
require 'jeni/options'
|
19
|
+
require 'jeni/errors'
|
20
|
+
require 'jeni/optparse'
|
21
|
+
|
22
|
+
require 'etc'
|
23
|
+
|
24
|
+
# = Jeni
|
25
|
+
#
|
26
|
+
# A simple installer designed to support gems by installing the things
|
27
|
+
# that gem cannot install. For example: sbin files, /etc files.
|
28
|
+
#
|
29
|
+
# To use Jeni, you need to create an instance in a block, use the various methods
|
30
|
+
# to copy files etc, and run the resulting block:
|
31
|
+
#
|
32
|
+
# Jeni::Installer.construct('my_gem) do |jeni|
|
33
|
+
# jeni.pretend
|
34
|
+
# jeni.file('source.rb', '/usr/sbin/target')
|
35
|
+
# jeni.file('etc/config.rb', '/etc/my_gem.rb', :chown=>'root')
|
36
|
+
# end.run!
|
37
|
+
#
|
38
|
+
module Jeni
|
39
|
+
|
40
|
+
# The main class to be used by callers to construct an installation script.
|
41
|
+
# See {file:README.md Readme} for full details.
|
42
|
+
#
|
43
|
+
# Included modules are:
|
44
|
+
#
|
45
|
+
# * Options - methods to allow the direct assignment of defaults etc
|
46
|
+
# * Optparse - process command line options using Optparse
|
47
|
+
# * Utils, Actions and IO - methods under the bonnet
|
48
|
+
class Installer
|
49
|
+
|
50
|
+
include Jeni::Utils
|
51
|
+
include Jeni::Actions
|
52
|
+
include Jeni::IO
|
53
|
+
include Jeni::Options
|
54
|
+
include Jeni::Optparse
|
55
|
+
|
56
|
+
extend Jeni::IO # get say for the class as well!
|
57
|
+
|
58
|
+
# create a jeni installer instance
|
59
|
+
#
|
60
|
+
# @param [String] source_root is the path to the source to copy from etc
|
61
|
+
# @param [String] app_name is the name of the app being installed
|
62
|
+
# @yield [self] returns self
|
63
|
+
def initialize(source_root, app_name)
|
64
|
+
@app_name = app_name
|
65
|
+
#@gem_spec = Gem::Specification.find_by_name(@gem_name)
|
66
|
+
#@gem_dir = @gem_spec.gem_dir
|
67
|
+
@source_root = source_root
|
68
|
+
@commands = []
|
69
|
+
@errors = {}
|
70
|
+
@owner = nil
|
71
|
+
@gem_dir = nil
|
72
|
+
if block_given? then
|
73
|
+
yield self
|
74
|
+
else
|
75
|
+
return self
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @private
|
80
|
+
def set_gem(dir)
|
81
|
+
@gem_dir = dir
|
82
|
+
@gem = true
|
83
|
+
end
|
84
|
+
|
85
|
+
# allow scripters to know where the gem directory is
|
86
|
+
attr_reader :gem_dir
|
87
|
+
|
88
|
+
#protected :set_gem
|
89
|
+
|
90
|
+
|
91
|
+
# construct an installer to install files etc after a gem install.
|
92
|
+
#
|
93
|
+
# @param [String] gem_name is the name of the gem to install from
|
94
|
+
# @yield [Jeni::Installer] an instance through which options and actions can
|
95
|
+
# be taken
|
96
|
+
#
|
97
|
+
def self.new_from_gem(gem_name)
|
98
|
+
gem_spec = Gem::Specification.find_by_name(gem_name)
|
99
|
+
installer = self.new(gem_spec.gem_dir, gem_name)
|
100
|
+
installer.set_gem(gem_spec.gem_dir)
|
101
|
+
if block_given? then
|
102
|
+
yield(installer)
|
103
|
+
end
|
104
|
+
return installer
|
105
|
+
rescue Gem::LoadError
|
106
|
+
say(:fatal, "Gem name #{gem_name} could not be found", :error)
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# action the commands, or not, depending on the options selected
|
111
|
+
def run!
|
112
|
+
if self.errors? then
|
113
|
+
unless @quiet
|
114
|
+
puts "There are errors in the installation, which has been cancelled:"
|
115
|
+
puts ""
|
116
|
+
end
|
117
|
+
|
118
|
+
self.each_error do |error, target|
|
119
|
+
|
120
|
+
say(error, target, :error)
|
121
|
+
end
|
122
|
+
return false
|
123
|
+
end
|
124
|
+
# no errors, so do something
|
125
|
+
@commands.each do |command|
|
126
|
+
verb = command.keys.first
|
127
|
+
args = command[verb]
|
128
|
+
case verb
|
129
|
+
when :mkdir
|
130
|
+
mkdir(args)
|
131
|
+
when :file
|
132
|
+
copy(args[0], args[1])
|
133
|
+
when :chown
|
134
|
+
chown(args[:file], args[:owner])
|
135
|
+
when :chgrp
|
136
|
+
chgrp(args[:file], args[:group])
|
137
|
+
when :chmod
|
138
|
+
chmod(args[:file], args[:mode])
|
139
|
+
when :generate
|
140
|
+
generate(args[0], args[1], args[2])
|
141
|
+
when :wrap
|
142
|
+
wrap(args[0], args[1], args[2])
|
143
|
+
when :link
|
144
|
+
link_it(args[0], args[1])
|
145
|
+
when :user
|
146
|
+
add_user(args[:name], args[:options])
|
147
|
+
when :group
|
148
|
+
add_group(args[:name], args[:options])
|
149
|
+
when :say
|
150
|
+
say(args[0], args[1], args[2])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
return true
|
155
|
+
|
156
|
+
rescue JeniError => err
|
157
|
+
puts "An error has occurred. Aborting."
|
158
|
+
puts err.inspect
|
159
|
+
err.each do |el|
|
160
|
+
puts el
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# check that a given file exists and report an error otherwise
|
165
|
+
#
|
166
|
+
# @param [String] path to the file that must exist
|
167
|
+
#
|
168
|
+
def exists?(path)
|
169
|
+
check_file(path)
|
170
|
+
end
|
171
|
+
|
172
|
+
# copy a file from the source, relative to the gem home to the target
|
173
|
+
# which is absolute
|
174
|
+
#
|
175
|
+
# @param [String] source is the gem-relative path to the file to copy
|
176
|
+
# @param [String] target is an absolute path to copy to
|
177
|
+
# @params [Hash] opts options for copying the file
|
178
|
+
# @option opts [String] :chown the name of the owner
|
179
|
+
# @option opts [String] :chgrp the name of the group
|
180
|
+
# @option opts [Octal] :chmod octal bit settings for chmod
|
181
|
+
#
|
182
|
+
# Note :chown and :chgrp override the global options e.g. {Jeni::Options#owner owner}
|
183
|
+
def file(source, target, opts={})
|
184
|
+
#gsource = File.join(@source_root, source)
|
185
|
+
#target_dir = File.dirname(target)
|
186
|
+
owner = opts[:chown] || @owner
|
187
|
+
chmod = opts[:chmod]
|
188
|
+
|
189
|
+
gsource = check_file(source)
|
190
|
+
check_target(target, owner)
|
191
|
+
check_chmod(chmod)
|
192
|
+
|
193
|
+
@commands << {:file => [gsource, target]}
|
194
|
+
process_options(opts, target)
|
195
|
+
end
|
196
|
+
|
197
|
+
# copy all of the files in the source directory to the target directory
|
198
|
+
#
|
199
|
+
# @param (see #file)
|
200
|
+
# @option (see #file)
|
201
|
+
#
|
202
|
+
def directory(source, target, opts={})
|
203
|
+
#gsource = File.join(@source_root, source)
|
204
|
+
gsource = check_file(source)
|
205
|
+
Dir["#{gsource}/*"].each do |sourcefile|
|
206
|
+
src_path = sourcefile.sub(@source_root + '/', '')
|
207
|
+
tgt_path = sourcefile.sub(gsource + '/', '')
|
208
|
+
targetfile = File.join(target, tgt_path)
|
209
|
+
if FileTest.directory?(sourcefile) then
|
210
|
+
self.directory(sourcefile, targetfile)
|
211
|
+
else
|
212
|
+
self.file(sourcefile, targetfile, opts)
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# create an empty directory and change the owner if specified. This ignores
|
219
|
+
# any nomkdir setting. Do not use this method together with directory as
|
220
|
+
# jeni will get confused about whether the directory exists or not!
|
221
|
+
#
|
222
|
+
# @param [String] target is the absolute path of the empty directory to create
|
223
|
+
# @param [Hash] opts to customise the directory
|
224
|
+
# @option (see #file)
|
225
|
+
#
|
226
|
+
def empty_directory(target, opts={})
|
227
|
+
@commands << {:mkdir => target}
|
228
|
+
owner = opts[:chown] || @owner
|
229
|
+
chmod = opts[:chmod]
|
230
|
+
process_options(opts, target)
|
231
|
+
end
|
232
|
+
|
233
|
+
# check that a file exists
|
234
|
+
#
|
235
|
+
# @param [String] file to test, relative to the source (e.g. gem)
|
236
|
+
# @param [Hash] options
|
237
|
+
# @option opts [Symbol] :executable to check if it is also executable
|
238
|
+
#
|
239
|
+
def file_exists?(file, opts={})
|
240
|
+
check_file(file)
|
241
|
+
check_executable(file) if opts.has_key?(:executable)
|
242
|
+
@commands << {:say => [:exists, file, :ok]}
|
243
|
+
end
|
244
|
+
|
245
|
+
# create a new user
|
246
|
+
#
|
247
|
+
# @param [String] name of the new user to create
|
248
|
+
# @param [Hash] options for creating the user
|
249
|
+
# @option opts [Integer] :uid user id instead of next available
|
250
|
+
# @option opts [Integer] :gid group id instead of next available
|
251
|
+
# @option opts [String] :home path to home directory for user, defaults to /home/$user
|
252
|
+
# @option opts [String] :shell path to shell for user, defaults to /bin/bash
|
253
|
+
# @option opts [Boolean] :skip set true to skip if user exists else fail
|
254
|
+
#
|
255
|
+
def user(name, opts={})
|
256
|
+
skip = opts[:skip]
|
257
|
+
skip = check_new_user(name, opts, skip)
|
258
|
+
check_root(skip)
|
259
|
+
opts[:skip] = skip
|
260
|
+
@commands << {:user => {:name => name, :options => opts}}
|
261
|
+
end
|
262
|
+
|
263
|
+
# create a new group
|
264
|
+
#
|
265
|
+
# @param [String] name of the new group to create
|
266
|
+
# @param [Hash] options for creating the user
|
267
|
+
# @option opts [Integer] :gid group id instead of next available
|
268
|
+
# @option opts [Boolean] :skip set true to skip if user exists else fail
|
269
|
+
#
|
270
|
+
def group(name, opts={})
|
271
|
+
skip = opts[:skip]
|
272
|
+
skip = check_new_group(name, opts, skip)
|
273
|
+
check_root(skip)
|
274
|
+
opts[:skip] = skip
|
275
|
+
@commands << {:group => {:name => name, :options => opts}}
|
276
|
+
end
|
277
|
+
|
278
|
+
# generate a file from a template using the Haml engine for plain text input.
|
279
|
+
# The template is prefixed with the ':plain' directive. Variables can be
|
280
|
+
# passed to the template using the locals hash (see below). See {file: README.md Readme}
|
281
|
+
# for more details.
|
282
|
+
#
|
283
|
+
# the file can be used for anything, with ruby code inserting as #\{code\}.
|
284
|
+
#
|
285
|
+
# @param [String] source is the path to the template to render
|
286
|
+
# @param [String] target is an absolute path to the generated file
|
287
|
+
# @param [Hash] locals is an optional hash that will be converted to local params within the template
|
288
|
+
#
|
289
|
+
# include :chown=>'user' in the locals to change the owner as well. It will be stripped from the locals
|
290
|
+
# passed to the template. Similarly for :chmod and :chgrp.
|
291
|
+
#
|
292
|
+
def template(source, target, locals={})
|
293
|
+
#gsource = File.join(@source_root, source)
|
294
|
+
#target_dir = File.dirname(target)
|
295
|
+
opts = Hash.new
|
296
|
+
opts[:chown] = locals.delete(:chown)
|
297
|
+
opts[:chmod] = locals.delete(:chmod)
|
298
|
+
opts[:chgrp] = locals.delete(:chgrp)
|
299
|
+
|
300
|
+
gsource = check_file(source)
|
301
|
+
check_target(target, opts[:owner])
|
302
|
+
check_chmod(opts[:chmod])
|
303
|
+
|
304
|
+
@commands << {:generate => [gsource, target, locals]}
|
305
|
+
process_options(opts, target)
|
306
|
+
end
|
307
|
+
|
308
|
+
# as for {Jeni::Installer#template} but searches for the source template from a list of predefined
|
309
|
+
# directories. These are ~/.jermine/templates and /usr/local/share/templates
|
310
|
+
#
|
311
|
+
# @param (see #template)
|
312
|
+
#
|
313
|
+
def standard_template(source, target, locals={})
|
314
|
+
# search for the template until it is found
|
315
|
+
gsource = get_template(source)
|
316
|
+
opts = Hash.new
|
317
|
+
opts[:chown] = locals.delete(:chown)
|
318
|
+
opts[:chmod] = locals.delete(:chmod)
|
319
|
+
opts[:chgrp] = locals.delete(:chgrp)
|
320
|
+
|
321
|
+
check_target(target, opts[:owner])
|
322
|
+
check_chmod(opts[:chmod])
|
323
|
+
|
324
|
+
@commands << {:generate => [gsource, target, locals]}
|
325
|
+
process_options(opts, target)
|
326
|
+
end
|
327
|
+
|
328
|
+
# create a wrapper script at target to call source in a similar manner to Gem's wrapper
|
329
|
+
#
|
330
|
+
# The wrapper follows the same rules as for a gem, so its shebang can be set by the original
|
331
|
+
# file being wrapped, or by :custom_shebang in your .gemrc
|
332
|
+
#
|
333
|
+
# @param [String] source is the relative path to the file to wrap
|
334
|
+
# @param [String] target is an absolute path for the wrapper
|
335
|
+
# @params [Hash] opts options for wrapping the file
|
336
|
+
# @option opts [String] :chown the name of the owner
|
337
|
+
#
|
338
|
+
def wrapper(source, target, opts={})
|
339
|
+
#gsource = File.join(@source_root, source)
|
340
|
+
owner = opts[:chown] || @owner
|
341
|
+
|
342
|
+
check_gem(:wrapper)
|
343
|
+
gsource = check_file(source)
|
344
|
+
check_target(target, owner)
|
345
|
+
|
346
|
+
@commands << {:wrap => [source, target, gsource]}
|
347
|
+
@commands << {:chmod => {:file => target, :mode => 0755}} if opts.has_key?(:chmod)
|
348
|
+
end
|
349
|
+
|
350
|
+
# create a link at target to source
|
351
|
+
#
|
352
|
+
# @param [String] source is the gem-relative path to the file to link
|
353
|
+
# @param [String] target is an absolute path for the link
|
354
|
+
#
|
355
|
+
def link(source, target)
|
356
|
+
#gsource = File.expand_path(File.join(@source_root, source))
|
357
|
+
gsource = check_file(source)
|
358
|
+
check_target(target)
|
359
|
+
@commands << {:link => [gsource, target]}
|
360
|
+
end
|
361
|
+
|
362
|
+
# output a message in the same format as other messages
|
363
|
+
#
|
364
|
+
# @param [String] action a single word describing the action taking place
|
365
|
+
# @param [String] message a short message concerning the action
|
366
|
+
# @param [Symbol] status can be :ok, :no_change, :warning or :error
|
367
|
+
#
|
368
|
+
def message(action, message, status)
|
369
|
+
@commands << {:say => [action, message, status]}
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
end
|
374
|
+
end
|