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

VERSION
    version 0.13

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. Another example is
    Setup::Unix::User, which will setup a Unix user (with the correct
    specified group membership). If everything is already correct, nothing
    is done (the function returns 304 status). In other words, setup
    function should be idempotent. One should be able to run it multiple
    times safely to reach the desired state.

    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.

SEE ALSO
    Other modules in Setup:: namespace.

FUNCTIONS
  setup_file(%args) -> [status, msg, result, meta]
    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, -undohint should contain {tmpdir=>...} to specify temporary
    directory to save replaced file/dir. Temporary directory defaults to
    ~/.setup, it will be created if not exists.

    Arguments ('*' denotes required arguments):

    *   allow_symlink* => *bool* (default: 1)

        Whether symlink is allowed.

        If existing file is a symlink then if allowsymlink is false then it
        is an unacceptable condition (the symlink will be replaced if
        replacesymlink 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 checkcontentcode & gencontentcode.

    *   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.

    *   path* => *str*

        Path to file.

        File path needs to be absolute so it's normalized.

    *   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.

    Return value:

    Returns an enveloped result (an array). First element (status) is an
    integer containing HTTP status code (200 means OK, 4xx caller error, 5xx
    function error). Second element (msg) is a string containing error
    message, or 'OK' if status is 200. Third element (result) is optional,
    the actual result. Fourth element (meta) is called result metadata and
    is optional, a hash that contains extra information.

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.

