NAME
Setup::File - Setup file (existence, mode, permission, content)
VERSION
version 0.11
SYNOPSIS
use Setup::File 'setup_file';
# simple usage (doesn't save undo data)
my $res = setup_file path => '/etc/rc.local',
should_exist => 1,
gen_content_code => sub { \("#!/bin/sh\n") },
owner => 'root', group => 0,
mode => '+x';
die unless $res->[0] == 200 || $res->[0] == 304;
# perform setup and save undo data (undo data should be serializable)
$res = setup_file ..., -undo_action => 'do';
die unless $res->[0] == 200 || $res->[0] == 304;
my $undo_data = $res->[3]{undo_data};
# perform undo
$res = setup_file ..., -undo_action => "undo", -undo_data=>$undo_data;
die unless $res->[0] == 200 || $res->[0] == 304;
DESCRIPTION
This module provides one function: setup_file.
This module is part of the Setup modules family.
This module uses Log::Any logging framework.
This module's functions have Sub::Spec specs.
THE SETUP MODULES FAMILY
I use the "Setup::" namespace for the Setup modules family. These family
of modules are typically used in installers. They must support
uninstallation (reverse/undo) and be flexible enough to deal with
external state changes.
To "setup" something means to set something into a desired state. For
example, Setup::File sets up a file with a specified permission mode,
ownership, and content. If the file doesn't exist it will be created; if
a directory exists instead, it will be removed and replaced with the
file; if the file already exists but with incorrect
permission/owner/content, it will be corrected. If everything is already
correct, nothing is done (the function returns 304 status). Another
example is Setup::Unix::User, which will setup a Unix user (with the
correct specified group membership).
A simulation (dry run) mode also exists: if you pass -dry_run => 1
argument to the function, it will check states and report
inconsistencies, but will modify nothing and return 200 status (or 304)
immediately instead. See the "dry_run" feature in
Sub::Spec::Clause::features for more details on dry running.
After the setup, the function returns undo data, which can be used to
perform undo ("unsetup", "uninstallation") later. The undo data is
serializable and thus can be stored in persistent storage. When doing
undo, the undo data is fed into the "-undo_data" argument, along with
other same arguments specified during the previous "do" phase. See the
"undo" feature in Sub::Spec::Clause::features for more details on the
undo protocol. Undo will reverse all actions done by the function in the
"do" phase; for example, if a file was created by the function it will
be deleted (if it hasn't changed since the creation), if an existing
file's mode/ownership was changed, it will be restored, and so on.
There could be various state changes between the time of do and undo; a
file can be deleted or modified by other processes. The undo must be
flexible enough so it can reverse whatever state changes the previous do
phase did whenever it can, but not disrupt other processes' changes.
After an undo, the function returns undo_data, which can be used to
perform undo of undo (redo) later.
Implementation
Below is the general view on implementation of a setup module. For more
details, delve directly into the source code.
We divide setup into a series of unit steps. For example, setting up a
file is comprised of steps: create, chown, chmod. Or it can just be:
set_content, chown, chmod, if the file already exists. Or: rm, create,
chown, chmod, if a directory exists and must be removed first.
To perform setup, we begin with an empty list of steps and add necessary
steps according to the current state. To perform undo, we are given undo
data, which is just the list of steps generated by previous invocation.
After we have the list of steps, we perform them one by one
sequentially. Each step comes with its own state checking and can be
skipped if the desired state is already reached. After performing a
step, we also add an undo step to the undo steps list. If an error is
encountered in a step, we can perform a rollback, which basically means
we perform the undo steps formed up to that point. (If error is
encountered during rollback, we die.)
After all steps have been done successfully, we return 200.
Undo data
Undo data should be a list of steps:
[undo_step1, undo_step2, ...]
Undo step is usually a command followed by a list of args, examples:
["reset"]
["rm", "file1"]
["do", "Setup::File::setup_file", {arg1=>..., arg2=>...}]
["undo", "Setup::File::setup_file", $args, $undo_data]
Because undo data might be needed much later after it is generated (e.g.
months or even years later when a software is finally uninstalled),
please plan a stable list of commands and its arguments carefully, so
much newer version of your setup module can still perform undo using
undo data produced by older version of your setup module. Existing
commands should still be supported as long as possible, unless
absolutely necessary that it is abandoned. Changes in the order of
command arguments should also be kept minimal.
FUNCTIONS
None are exported by default, but they are exportable.
setup_file(%args) -> [STATUS_CODE, ERR_MSG, RESULT]
Setup file (existence, mode, permission, content).
On do, will create file (if it doesn't already exist) and correct
mode/permission as well as content.
On undo, will restore old mode/permission/content, or delete the file
again if it was created by this function *and* its content hasn't
changed since.
If given, -undo_hint should contain {tmp_dir=>...} to specify temporary
directory to save replaced file/dir. Temporary directory defaults to
~/.setup, it will be created if not exists.
Returns a 3-element arrayref. STATUS_CODE is 200 on success, or an error
code between 3xx-5xx (just like in HTTP). ERR_MSG is a string containing
error message, RESULT is the actual result.
This function supports undo operation. See Sub::Spec::Clause::features
for details on how to perform do/undo/redo.
This function supports dry-run (simulation) mode. To run in dry-run
mode, add argument "-dry_run" => 1.
Arguments ("*" denotes required arguments):
* path* => *str*
Path to file.
File path needs to be absolute so it's normalized.
* allow_symlink* => *bool* (default 1)
Whether symlink is allowed.
If existing file is a symlink then if allow_symlink is false then it
is an unacceptable condition (the symlink will be replaced if
replace_symlink is true).
Note: if you want to setup symlink instead, use Setup::Symlink.
* check_content_code => *code*
Code to check content.
If unset, file will not be checked for its content. If set, code
will be called whenever file content needs to be checked. Code will
be passed the reference to file content and should return a boolean
value indicating whether content is acceptable. If it returns a
false value, content is deemed unacceptable and needs to be fixed.
Alternatively you can use the simpler 'content' argument.
* content => *str*
Desired file content.
Alternatively you can also use check_content_code &
gen_content_code.
* gen_content_code => *code*
Code to generate content.
If set, whenever a new file content is needed (e.g. when file is
created or file content reset), this code will be called to provide
it. If unset, empty string will be used instead.
Code will be passed the reference to the current content (or undef)
and should return the new content.
Alternatively you can use the simpler 'content' argument.
* group => *str*
Expected group.
* mode => *str*
Expected permission mode.
* owner => *str*
Expected owner.
* replace_dir* => *bool* (default 1)
Replace existing dir if it needs to be replaced.
* replace_file* => *bool* (default 1)
Replace existing file if it needs to be replaced.
* replace_symlink* => *bool* (default 1)
Replace existing symlink if it needs to be replaced.
* should_exist => *bool*
Whether file should exist.
If undef, file need not exist. If set to 0, file must not exist and
will be deleted if it does. If set to 1, file must exist and will be
created if it doesn't.
SEE ALSO
Other modules in Setup:: namespace.
AUTHOR
Steven Haryanto <stevenharyanto@gmail.com>
COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by Steven Haryanto.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.