Madagascar / scripts / deploy-project.sh
Newer Older
f16725e 3 months ago History
256 lines | 6.208kb
Bogdan Timofte authored 3 months ago
1
#!/bin/bash
2

            
3
set -euo pipefail
4

            
5
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
7
CONFIG_PATH="${ROOT_DIR}/cluster-context/madagascar.json"
8
CLUSTER_NAME="madagascar"
9
COMMAND="install"
10
REMOTE_USER="root"
11
DRY_RUN=0
12
PROJECT_NAME=""
13
PROJECT_DIR=""
14
DEPLOY_MODE=""
15
NODE_FILTERS=()
16
TARGETS=()
17

            
18
usage() {
19
    cat <<EOF
20
Usage: $0 <project> [command] [options]
21

            
22
Commands:
23
  install      Deploy/install project on selected nodes (default)
24
  uninstall    Remove project from selected nodes
25
  status       Query project status on selected nodes (deploy.sh projects only)
26
  start        Start services on selected nodes (deploy.sh projects only)
27
  restart      Restart services on selected nodes (deploy.sh projects only)
28
  stop         Stop services on selected nodes (deploy.sh projects only)
29

            
30
Options:
31
  --cluster <name>    Cluster name in cluster-context/madagascar.json (default: madagascar)
32
  --node <name|ip>    Restrict to one node. Can be repeated.
33
  --user <user>       Remote SSH user for setup.sh projects (default: root)
34
  --dry-run           Show resolved targets and commands without executing
35
  -h, --help          Show this help
36

            
37
Examples:
38
  $0 pve-guests-state
39
  $0 pve-guests-state install --node ebony
40
  $0 autoNAS install
41
  $0 autoSMART status --node 192.168.2.92
42
EOF
43
}
44

            
45
require_config() {
46
    if [[ ! -f "${CONFIG_PATH}" ]]; then
47
        echo "ERROR: missing cluster config: ${CONFIG_PATH}" >&2
48
        exit 1
49
    fi
50
}
51

            
52
require_project() {
53
    PROJECT_DIR="${ROOT_DIR}/projects/${PROJECT_NAME}"
54
    if [[ ! -d "${PROJECT_DIR}" ]]; then
55
        echo "ERROR: unknown project: ${PROJECT_NAME}" >&2
56
        exit 1
57
    fi
58

            
59
    if [[ -x "${PROJECT_DIR}/setup.sh" ]]; then
60
        DEPLOY_MODE="setup"
61
        return
62
    fi
63

            
64
    if [[ -x "${PROJECT_DIR}/deploy.sh" ]]; then
65
        DEPLOY_MODE="deploy"
66
        return
67
    fi
68

            
69
    echo "ERROR: project ${PROJECT_NAME} has neither setup.sh nor deploy.sh" >&2
70
    exit 1
71
}
72

            
73
load_targets() {
74
    local entry=""
75
    TARGETS=()
76

            
77
    while IFS= read -r entry; do
78
        [[ -n "${entry}" ]] && TARGETS+=("${entry}")
79
    done < <(
80
        jq -r --arg cluster "${CLUSTER_NAME}" '
81
            .clusters[$cluster].nodes
82
            | to_entries[]
83
            | .key + "\t" + ((.value.ip // .value.wan.vmbr443.address // empty) | split("/")[0])
84
        ' "${CONFIG_PATH}"
85
    )
86

            
87
    if [[ ${#TARGETS[@]} -eq 0 ]]; then
88
        echo "ERROR: no targets found for cluster ${CLUSTER_NAME}" >&2
89
        exit 1
90
    fi
91
}
92

            
93
match_filter() {
94
    local filter="$1"
95
    local node_name="$2"
96
    local node_ip="$3"
97

            
98
    [[ "${filter}" == "${node_name}" || "${filter}" == "${node_ip}" ]]
99
}
100

            
101
filter_targets() {
102
    local filtered=()
103
    local entry=""
104
    local filter=""
105
    local node_name=""
106
    local node_ip=""
107

            
108
    if [[ ${#NODE_FILTERS[@]} -eq 0 ]]; then
109
        return
110
    fi
111

            
112
    for entry in "${TARGETS[@]}"; do
113
        node_name="${entry%%$'\t'*}"
114
        node_ip="${entry#*$'\t'}"
115
        for filter in "${NODE_FILTERS[@]}"; do
116
            if match_filter "${filter}" "${node_name}" "${node_ip}"; then
117
                filtered+=("${entry}")
118
                break
119
            fi
120
        done
121
    done
122

            
123
    TARGETS=("${filtered[@]}")
124

            
125
    if [[ ${#TARGETS[@]} -eq 0 ]]; then
126
        echo "ERROR: no targets matched the provided --node filters" >&2
127
        exit 1
128
    fi
129
}
130

            
131
run_setup_project() {
132
    local node_name="$1"
133
    local node_ip="$2"
134
    local cmd=()
135

            
136
    case "${COMMAND}" in
137
        install)
138
            cmd=(bash "${PROJECT_DIR}/setup.sh" --user "${REMOTE_USER}" "${node_ip}")
139
            ;;
140
        uninstall)
141
            cmd=(bash "${PROJECT_DIR}/setup.sh" --user "${REMOTE_USER}" --uninstall "${node_ip}")
142
            ;;
143
        *)
144
            echo "ERROR: command ${COMMAND} is not supported for setup.sh-only projects" >&2
145
            exit 1
146
            ;;
147
    esac
148

            
149
    echo "==> ${PROJECT_NAME}: ${COMMAND} on ${node_name} (${node_ip})"
150
    if [[ "${DRY_RUN}" -eq 1 ]]; then
151
        printf 'DRY-RUN:'
152
        printf ' %q' "${cmd[@]}"
153
        echo
154
        return
155
    fi
156

            
157
    (cd "${PROJECT_DIR}" && "${cmd[@]}")
158
}
159

            
160
run_deploy_project() {
161
    local node_name="$1"
162
    local node_ip="$2"
163
    local cmd=(bash "${PROJECT_DIR}/deploy.sh" "${COMMAND}" "${node_ip}")
164

            
165
    echo "==> ${PROJECT_NAME}: ${COMMAND} on ${node_name} (${node_ip})"
166
    if [[ "${DRY_RUN}" -eq 1 ]]; then
167
        printf 'DRY-RUN:'
168
        printf ' %q' "${cmd[@]}"
169
        echo
170
        return
171
    fi
172

            
173
    (cd "${PROJECT_DIR}" && "${cmd[@]}")
174
}
175

            
176
parse_args() {
177
    if [[ $# -lt 1 ]]; then
178
        usage
179
        exit 1
180
    fi
181

            
182
    PROJECT_NAME="$1"
183
    shift
184

            
185
    if [[ $# -gt 0 ]]; then
186
        case "$1" in
187
            install|uninstall|status|start|restart|stop)
188
                COMMAND="$1"
189
                shift
190
                ;;
191
        esac
192
    fi
193

            
194
    while [[ $# -gt 0 ]]; do
195
        case "$1" in
196
            --cluster)
197
                CLUSTER_NAME="$2"
198
                shift 2
199
                ;;
200
            --node)
201
                NODE_FILTERS+=("$2")
202
                shift 2
203
                ;;
204
            --user)
205
                REMOTE_USER="$2"
206
                shift 2
207
                ;;
208
            --dry-run)
209
                DRY_RUN=1
210
                shift
211
                ;;
212
            -h|--help)
213
                usage
214
                exit 0
215
                ;;
216
            *)
217
                echo "ERROR: unknown option: $1" >&2
218
                usage
219
                exit 1
220
                ;;
221
        esac
222
    done
223
}
224

            
225
main() {
226
    parse_args "$@"
227
    require_config
228
    require_project
229
    load_targets
230
    filter_targets
231

            
232
    echo "Project: ${PROJECT_NAME}"
233
    echo "Mode: ${DEPLOY_MODE}"
234
    echo "Command: ${COMMAND}"
235
    echo "Cluster: ${CLUSTER_NAME}"
236
    echo "Targets:"
237
    printf '  %s\n' "${TARGETS[@]}"
238
    echo
239

            
240
    local entry=""
241
    local node_name=""
242
    local node_ip=""
243

            
244
    for entry in "${TARGETS[@]}"; do
245
        node_name="${entry%%$'\t'*}"
246
        node_ip="${entry#*$'\t'}"
247
        if [[ "${DEPLOY_MODE}" == "setup" ]]; then
248
            run_setup_project "${node_name}" "${node_ip}"
249
        else
250
            run_deploy_project "${node_name}" "${node_ip}"
251
        fi
252
        echo
253
    done
254
}
255

            
256
main "$@"