-
Notifications
You must be signed in to change notification settings - Fork 0
285 lines (261 loc) · 11.1 KB
/
remote-copy.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
name: Config Inputs Remote Copy
run-name: "${{ inputs.remote-environment }} Config Input Copy to ${{ inputs.target }}"
on:
workflow_dispatch:
inputs:
remote-environment:
type: choice
required: true
description: The Github Environment for the given remote
options:
- Gadi
- Gadi Prerelease
source:
type: string
required: true
description: Remote absolute path to configuration input source
target:
type: string
required: true
description: Remote absolute path to configuration input destination
overwrite-target:
type: boolean
required: true
description: Overwrite the remote target if it already exists
target-acl-spec:
type: string
required: true
# Default to no write for everyone except tm70_ci
# TODO: This default will probably not work for other `remote-environment`s
default: >-
u::rwx,
u:tm70_ci:rwx,
g::r-x,
m::rwx,
o::---,
d:u::rwx,
d:u:tm70_ci:rwx,
d:g::r-x,
d:m::rwx,
d:o::---
description: ACL spec to be passed to `setfacl -m` for the given target
store-on-tape:
type: boolean
required: true
default: true
description: Also store target on the remotes cold storage service
jobs:
setup:
name: Setup
runs-on: ubuntu-latest
outputs:
# The `inputs.target-acl-spec` with spaces removed
formatted-acl: ${{ steps.fmt.outputs.acl }}
steps:
- name: Log inputs
run: |
echo "::notice::Copy on ${{ inputs.remote-environment }} from '${{ inputs.source }}' to '${{ inputs.target }}' with ACLs '${{ inputs.target-acl-spec }}'"
echo "::${{ inputs.overwrite-target && 'warning' || 'notice' }}::This operation ${{ inputs.overwrite-target && 'WILL' || 'will not' }} overwrite ${{ inputs.target }}"
- name: Verify inputs
run: |
errors=false
if [ -z "${{ inputs.source }}"]; then
echo "::error::No 'source' input given, can't copy anything."
errors=true
fi
if [ -z "${{ inputs.target }}"]; then
echo "::error::No 'target' input given, can't copy to anywhere."
errors=true
fi
if [ -z "${{ inputs.target-acl-spec }}" ]; then
echo "::notice::No 'ACL' input given, not setting the ACLs explicitly."
fi
if [[ "$errors" == "true" ]]; then
echo "::error::Errors above, exiting..."
exit 1
fi
- name: Format ACL
id: fmt
# Remove spaces from ACL string as we have later logic that relies on
# the IFS=,
run: |
acl=$(echo '${{ inputs.target-acl-spec }}' | tr -d ' ')
echo "Formatted ACL: $acl"
echo "acl=$acl" >> $GITHUB_OUTPUT
test-acl:
name: Test ACL
runs-on: ubuntu-latest
if: inputs.target-acl-spec != ''
needs:
- setup
container: rockylinux/rockylinux:8.10
env:
TEST_DIR: /opt/test
steps:
- name: Create Users in ACL String
# We don't error out here as it could have been because we are adding the same user twice
run: |
set +e
acl="${{ needs.setup.outputs.formatted-acl }}"
IFS=,
for entry in $acl; do
echo "Testing ACL for u(ser): $entry"
if [[ $entry =~ ^u(ser)?:([^:]+): ]]; then
user="${BASH_REMATCH[2]}"
echo "Adding user $user"
useradd $user
fi
done
- name: Create Groups in ACL String
# We don't error out here as it could have been because we are adding the same group twice
run: |
set +e
acl="${{ needs.setup.outputs.formatted-acl }}"
IFS=,
for entry in $acl; do
if [[ $entry =~ ^g(roup)?:([^:]+): ]]; then
echo "Testing ACL for g(roup): $entry"
group="${BASH_REMATCH[2]}"
echo "Adding group $group"
groupadd $group
fi
done
- name: Verify Valid ACL Spec
# Now that we have created the users and groups from the ACL string, check if it is valid!
run: |
mkdir ${{ env.TEST_DIR }}
echo "---- Users ----"
cut -d: -f1 /etc/passwd
echo "---- Groups ----"
groups
if setfacl --test --recursive --modify "${{ needs.setup.outputs.formatted-acl }}" ${{ env.TEST_DIR }}; then
echo "::notice::ACL Verification Successful. This does not test that the users/groups exist on the remote environment"
else
echo "::error::ACL Verification Failed. Check the preceding lines."
exit 1
fi
copy-to-remote:
name: Copy To ${{ inputs.remote-environment }}
runs-on: ubuntu-latest
needs:
- setup
- test-acl
environment: ${{ inputs.remote-environment }}
outputs:
# Space-separated list of paths copied to the target
files: ${{ steps.copy.outputs.paths }}
# Space-separated list of manifests created at the target
manifests: ${{ steps.manifest.outputs.paths }}
steps:
- name: Setup SSH
id: ssh
uses: access-nri/actions/.github/actions/setup-ssh@main
with:
private-key: ${{ secrets.SSH_KEY }}
hosts: |
${{ secrets.SSH_HOST }}
${{ secrets.SSH_HOST_DATA }}
- name: Verify Remote Target
run: |
if [[ "${{ startsWith(inputs.target, vars.CONFIGS_INPUT_DIR) }}" == "false" ]]; then
echo "::error::Remote target '${{ inputs.target }}' doesn't look like a configurations input directory."
exit 1
fi
- name: Rsync Source to Target
id: copy
# output:
# paths: space-separated list of files copied
env:
REMOTE_RSYNC_FILE_LIST_PATH: ${{ vars.REMOTE_TMP_DIR }}/remote-copy-files-${{ github.run_id }}.log
LOCAL_RSYNC_FILE_LIST_PATH: ./files-copied.log
# In this step, we rsync the files from the source to the target, capturing the list of files copied.
# We also remove the empty directories copied, and make it space-separated.
# There are two rsync steps, one for the rsyncing of source to target,
# and then one locally on the runner to copy the list of files from the remote.
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} -i ${{ steps.ssh.outputs.private-key-path }} /bin/bash <<'EOT'
rsync --recursive --out-format="${{ inputs.target }}/%n" \
${{ ! inputs.overwrite-target && '--ignore-existing' || '--update' }} \
${{ inputs.source }} ${{ inputs.target }} \
| grep --invert-match '/$' \
| tr '\n' ' ' \
| tee ${{ env.REMOTE_RSYNC_FILE_LIST_PATH }}
EOT
rsync -e 'ssh -i ${{ steps.ssh.outputs.private-key-path }}' \
${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST_DATA }}:${{ env.REMOTE_RSYNC_FILE_LIST_PATH }} \
${{ env.LOCAL_RSYNC_FILE_LIST_PATH }}
echo "paths=$(cat ${{ env.LOCAL_RSYNC_FILE_LIST_PATH }})" >> $GITHUB_OUTPUT
- name: Set ACLs on Target
if: inputs.target-acl-spec != ''
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} -i ${{ steps.ssh.outputs.private-key-path }} /bin/bash <<'EOT'
setfacl --recursive --modify "${{ needs.setup.outputs.formatted-acl }}" ${{ inputs.target }}
getfacl -t ${{ inputs.target }}
EOT
- name: Update Manifests
id: manifest
# output:
# paths: space-separated list of manifests created
env:
REMOTE_MANIFEST_FILE_LIST_PATH: ${{ vars.REMOTE_TMP_DIR }}/remote-copy-manifests-${{ github.run_id }}.log
LOCAL_MANIFEST_FILE_LIST_PATH: ./manifests-copied.log
MANIFEST_FILE_NAME: manifest.yaml
# Generate manifests for files copied over in the earlier rsync job.
# Similar to the copy step, we generate a list of manifest paths created on the remote,
# then copy that to the runner locally so we can set it as output.
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST_DATA }} -i ${{ steps.ssh.outputs.private-key-path }} /bin/bash <<'EOT'
module use ${{ vars.YAMF_MODULE_PATH }}
module load ${{ vars.YAMF_MODULE_NAME }}
declare -A manifest_paths
for path in ${{ steps.copy.outputs.paths }}; do
echo "Path is $path"
manifest_dir=$(dirname $path)
cd $manifest_dir || exit
manifest_entry_filename=$(basename $path)
echo "Generating a manifest entry in $manifest_dir for $manifest_entry_filename"
yamf add -n ${{ env.MANIFEST_FILE_NAME }} --force $manifest_entry_filename
manifest_paths["$manifest_dir/${{ env.MANIFEST_FILE_NAME }}"]=1
done
echo "${!manifest_paths[@]}" > ${{ env.REMOTE_MANIFEST_FILE_LIST_PATH }}
EOT
rsync -e 'ssh -i ${{ steps.ssh.outputs.private-key-path }}' \
${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST_DATA }}:${{ env.REMOTE_MANIFEST_FILE_LIST_PATH }} \
${{ env.LOCAL_MANIFEST_FILE_LIST_PATH }}
echo "paths=$(cat ${{ env.LOCAL_MANIFEST_FILE_LIST_PATH }})" >> $GITHUB_OUTPUT
copy-to-tape-gadi:
name: Copy To Tape On ${{ inputs.remote-environment }}
runs-on: ubuntu-latest
needs:
- copy-to-remote
if: inputs.store-on-tape && startsWith(inputs.remote-environment, 'Gadi')
environment: ${{ inputs.remote-environment }}
steps:
- name: Setup SSH
id: ssh
uses: access-nri/actions/.github/actions/setup-ssh@main
with:
private-key: ${{ secrets.SSH_KEY }}
hosts: |
${{ secrets.SSH_HOST }}
${{ secrets.SSH_HOST_DATA }}
- name: Send Target to Tape
# For each of the absolute paths of copied files and manifests, put
# the file (and it's directories relative to inputs.target) on the tape storage
# under a datestamp directory.
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} -i ${{ steps.ssh.outputs.private-key-path }} /bin/bash <<'EOT'
now=$(date +%Y_%m_%d_%H_%M)
mdss -P ${{ vars.PROJECT_CODE }} mkdir -p ${{ vars.TAPE_ROOT_DIR }}/$now
for file in ${{ needs.copy-to-remote.outputs.files }} ${{ needs.copy-to-remote.outputs.manifests }}; do
file_relative_to_target=${file#${{ inputs.target }}/}
echo "Moving '$file' to '${{ vars.TAPE_ROOT_DIR }}/$now/$file_relative_to_target'"
dirs_after_target=$(dirname $file_relative_to_target)
if [[ "$dirs_after_target" != "." ]]; then
mdss -P ${{ vars.PROJECT_CODE }} mkdir -p ${{ vars.TAPE_ROOT_DIR }}/$now/$(dirname $file_relative_to_target)
fi
mdss -P ${{ vars.PROJECT_CODE }} put -r $file ${{ vars.TAPE_ROOT_DIR }}/$now/$file_relative_to_target
done
echo "Output:"
mdss -P ${{ vars.PROJECT_CODE }} ls -R ${{ vars.TAPE_ROOT_DIR }}/$now
EOT