nginx-conf-flatten 7.52 KB
Newer Older
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#!/bin/bash

#
# flatten an nginx config
# 
# replaces all occurences of the `include` directive
# with the relevant included files
#
# assumptions:
# - include file paths do not contain spaces
#   (this seems compatible with how nginx handles includes)
# - no files are included from above the directory where the nginx config file
#   being processed resides in

Michał 'rysiek' Woźniak's avatar
bugfix  
Michał 'rysiek' Woźniak committed
15 16 17 18
# set debug verbosity to none
[ -z "${NGINX_CONFIG_FLATTEN_DEBUG+x}" ] && NGINX_CONFIG_FLATTEN_DEBUG=""


19
function debug {
Michał 'rysiek' Woźniak's avatar
bugfix  
Michał 'rysiek' Woźniak committed
20 21 22
    if [ "$NGINX_CONFIG_FLATTEN_DEBUG" != "" ]; then
        echo "$@" >&2
    fi
23 24
}

Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
25 26

#
27 28 29 30 31 32 33 34 35 36 37 38 39
# get the path relative to a different base path
# if the former is relative
# 
# $1 - path
# $2 - base path
function get-path-relative-to {
    # if the path is absolute, that's all we need
    if [[ "$1" = /* ]]; then
        echo "$1"
    else
        echo "$2/$1"
    fi
}
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64


# 
# extract the include paths from an nginx config file
# 
# $1 - the file to work with
function nginx-extract-includes {
    # assumption: include path does not contain spaces
    egrep '^\s*include ' "$1" | sed -r -e "s/^\s*include\s*([^;]+).*$/\1/"
}

# 
# handle all includes of a file to the temporary workdir
# creating the directory structure in the process
# and adding them to the NGINX_CONFIG_FILES variable
# for further processing
# 
# $1 - the file to work with
function nginx-handle-includes {
    # get the included files
    # this will loop through actual globbed filenames
    # 
    # assumption: no files are included from directories above the directory
    # the main config file resides in
    for i in $( nginx-extract-includes "$1" ); do
65
        debug -n "        +-- found include: $i"
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
66 67
        # if the temporary copy of the file does not exist
        if [ ! -e "$TMP_WORKDIR/$i" ]; then
68
            debug " - copying!"
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
69 70 71 72 73 74 75
            # create the directory structure
            mkdir -p "$TMP_WORKDIR/$( dirname "$i" )"
            # copy the file
            cp "$NGINX_CONFIG_DIR/$i" "$TMP_WORKDIR/$i"
            # add to the list of files to proces
            NGINX_CONFIG_ELEMENTS+=("$i")
        else
76
            debug " - already copied."
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
77 78 79 80 81
        fi
    done
}


82 83 84 85 86 87 88
#
# generate a cleaned nginx config directory,
# containing only the files that are included from the config file
# passed as the argument, or files included from those, etc
# 
# $NGINX_CONFIG - nginx config file to work off of
# $OUTPUT_DEST  - target directory (must *not* exist)
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
89 90 91 92 93 94 95 96 97 98
function nginx-clean-directory {
    # let's get the file in a temporary location
    # (we don't want to screw up the original!)
    cp "$NGINX_CONFIG" "$TMP_WORKDIR"/

    # go one by one over the NGINX_CONFIG_ELEMENTS array
    # but from the end
    while [[ ${#NGINX_CONFIG_ELEMENTS[@]} > 0 ]]; do
        CUR_INDEX=$(( ${#NGINX_CONFIG_ELEMENTS[@]} - 1 ))
        CUR_ELEMENT=${NGINX_CONFIG_ELEMENTS[$CUR_INDEX]}
99 100
        debug "    +-- working with index: $CUR_INDEX"
        debug "        $CUR_ELEMENT"
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
101 102 103
        unset "NGINX_CONFIG_ELEMENTS[$CUR_INDEX]"
        nginx-handle-includes "$CUR_ELEMENT";
    done
104 105 106 107 108 109
    
    # move the temporary work dir to the output location
    debug "+-- moving the temporary output directory to the final output location"
    debug "    src: $TMP_WORKDIR"
    debug "    dst: $OUTPUT_DEST"
    mv "$TMP_WORKDIR" "$OUTPUT_DEST"
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
110 111 112
}


113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
#
# flatten an nginx config file,
# including all includes inline, recursively
# 
# $NGINX_CONFIG - nginx config file to work off of
# $OUTPUT_DEST  - where to output the assembled, flattened file (must not exist)
function nginx-flatten {
    
    # let's get the file in a temporary location
    # (we don't want to screw up the original!)
    cp "$NGINX_CONFIG" "$TMP_WORKDIR"/
    NGINX_CONFIG_COPY="$TMP_WORKDIR/$( basename "$NGINX_CONFIG")"

    OLDIFS="$IFS"
    IFS=$'\n'
    while CURRENT_INCLUDES="$( egrep -n '^\s*include\s*.+;.*$' "$NGINX_CONFIG_COPY" )"; do
        PREVIOUS_CI_LINE=0;
        for ci in $CURRENT_INCLUDES; do
            # get the actual file glob
            CI_FILE_GLOB="$( echo "$ci" | sed -r -e "s/^[0-9]+\:\s*include\s*([^;]+).*$/\1/" )"
            CI_LINE="$( echo "$ci" | cut -d ':' -f 1 )"
134
            CI_INDENT="$( echo "$ci" | cut -d ':' -f 2 | sed -r -e 's/^(\s*).*$/\1/' )"
135
            DATE_EXT="$( date +%s%N )"
136 137 138
            debug "    +-- previous line : $PREVIOUS_CI_LINE"
            debug "        ci line       : $CI_LINE"
            debug "        indent        : '$CI_INDENT'"
Michał 'rysiek' Woźniak's avatar
bugfix  
Michał 'rysiek' Woźniak committed
139
            debug "        glob          : '$CI_FILE_GLOB'"
140
            tail -n "+$(( $PREVIOUS_CI_LINE + 1 ))" "$NGINX_CONFIG_COPY" | head -n $(( $CI_LINE - 1 - $PREVIOUS_CI_LINE )) > "$NGINX_CONFIG_COPY.$DATE_EXT"
141 142 143
            echo -e "\n$CI_INDENT############################################################\n$CI_INDENT### $CI_FILE_GLOB \n$CI_INDENT############################################################" >> "$NGINX_CONFIG_COPY.$DATE_EXT"
            sed -r -e "s/^/$CI_INDENT/" $CI_FILE_GLOB >> "$NGINX_CONFIG_COPY.$DATE_EXT"
            echo -e "\n$CI_INDENT############################################################\n$CI_INDENT### end $CI_FILE_GLOB \n$CI_INDENT############################################################" >> "$NGINX_CONFIG_COPY.$DATE_EXT"
144 145
            PREVIOUS_CI_LINE="$CI_LINE"
        done
Michał 'rysiek' Woźniak's avatar
bugfix  
Michał 'rysiek' Woźniak committed
146 147 148 149
        debug "    +-- previous line : $PREVIOUS_CI_LINE"
        debug "        finishing this run off."
        DATE_EXT="$( date +%s%N )"
        tail -n "+$(( $PREVIOUS_CI_LINE + 1 ))" "$NGINX_CONFIG_COPY" > "$NGINX_CONFIG_COPY.$DATE_EXT"
150 151 152 153
        cat $NGINX_CONFIG_COPY.* > "$NGINX_CONFIG_COPY"
        rm $NGINX_CONFIG_COPY.*
    done
    IFS="$OLDIFS"
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
154

155 156
    debug "+-- moving the temporary output file to the output destination"
    debug "    src: $NGINX_CONFIG_COPY"
157 158
    debug "    dst: $OUTPUT_DEST"
    mv "$NGINX_CONFIG_COPY" "$OUTPUT_DEST"
159
    debug "+-- cleaning up the temporary output directory"
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
160
    rm -rf "$TMP_WORKDIR"
161 162 163 164 165 166
}

function print-usage {
    cat - <<HEREDOC
    
usage:
167
    $0 <mode> <input_file> <output_file|output_directory>
168 169 170 171 172 173 174 175 176
    
modes:
    
    flatten:
        flatten an nginx config file by inlining all includes recursively,
        save to <output_file>
        
    clean-directory:
        generate a cleaned nginx config directory, containing only the files
177
        that are included from the input file, or files included from those,
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
        and so on; output to <output_directory>
    
HEREDOC
}


ORIG_CWD="$PWD"

# we really need 3 arguments:
# $1 - mode of operation
# $2 - source file
# $3 - destination file/directory

if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ]; then
    print-usage
    exit 1
fi

# source file exists?
if [ ! -f "$2" ] || [ ! -r "$2" ]; then
    echo
    echo "ERROR: source config file $2 doesn't exist or read access not granted"
    echo
    exit 2
Michał 'rysiek' Woźniak's avatar
Michał 'rysiek' Woźniak committed
202
fi
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240

# destination file/directory must not exist
OUTPUT_DEST="$( get-path-relative-to "$3" "$ORIG_CWD" )"
if [ -e "$OUTPUT_DEST" ]; then
    echo
    echo "WARNING: output file/directory exists; quitting!"
    echo
    exit 3
fi


NGINX_CONFIG="$( realpath -eL "$2" )"
debug "+-- config file: '$NGINX_CONFIG'"

NGINX_CONFIG_DIR="$( dirname "$NGINX_CONFIG" )"
debug "    config directory: 'NGINX_CONFIG_DIR'"

cd "$NGINX_CONFIG_DIR"

TMP_WORKDIR="$( mktemp -d /tmp/nginx-conf-flatten.XXXX )"
debug "+-- TMP_WORKDIR: $TMP_WORKDIR"

# the list of files whose includes we have not copied yet
NGINX_CONFIG_ELEMENTS=("$NGINX_CONFIG")


# what are we doing, eh?
if [ "$1" == "flatten" ]; then
    nginx-flatten "$NGINX_CONFIG" "$3"
elif [ "$1" == "clean-directory" ]; then
    nginx-clean-directory "$NGINX_CONFIG" "$3"
else
    print-usage
    exit 1
fi

# get back to the original directory we've been running from
cd "$ORIG_CWD"