The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Setup::File - Setup file (existence, mode, permission, content)

VERSION

version 0.12

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 Rinci metadata.

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 Rinci::function 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 Rinci::function 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.