podman

Форк
0
/
060-mount.bats 
514 строк · 21.7 Кб
1
#!/usr/bin/env bats
2

3
load helpers
4

5
@test "podman mount - basic test" {
6
    # Only works with root (FIXME: does it work with rootless + vfs?)
7
    skip_if_rootless "mount does not work rootless"
8
    skip_if_remote "mounting remote is meaningless"
9

10
    f_path=/tmp/tmpfile_$(random_string 8)
11
    f_content=$(random_string 30)
12

13
    c_name=mount_test_$(random_string 5)
14
    run_podman run --name $c_name $IMAGE \
15
               sh -c "echo $f_content > $f_path"
16

17
    run_podman mount $c_name
18
    mount_path=$output
19

20
    test -d $mount_path
21
    test -e "$mount_path/$f_path"
22
    is $(< "$mount_path/$f_path") "$f_content" "contents of file, as read via fs"
23

24
    # Make sure that 'podman mount' (no args) returns the expected path
25
    run_podman mount --notruncate
26
    # FIXME: is it worth the effort to validate the CID ($1) ?
27
    reported_mountpoint=$(echo "$output" | awk '{print $2}')
28
    is "$reported_mountpoint" "$mount_path" "mountpoint reported by 'podman mount'"
29

30
    # umount, and make sure files are gone
31
    run_podman umount $c_name
32
    if [[ -e "$mount_path/$f_path" ]]; then
33
        # With vfs, umount is a NOP: the path always exists as long as the
34
        # container exists. But with overlay, umount should truly remove.
35
        if [[ "$(podman_storage_driver)" != "vfs" ]]; then
36
            die "Mounted file exists even after umount: $mount_path/$f_path"
37
        fi
38
    fi
39

40
    # Remove the container. Now even with vfs the file must be gone.
41
    run_podman rm $c_name
42
    if [[ -e "$mount_path/$f_path" ]]; then
43
        die "Mounted file exists even after container rm: $mount_path/$f_path"
44
    fi
45
}
46

47

48
@test "podman image mount" {
49
    skip_if_remote "mounting remote is meaningless"
50
    skip_if_rootless "too hard to test rootless"
51

52
    # Start with clean slate
53
    run_podman image umount -a
54

55
    # Get full image ID, to verify umount
56
    run_podman image inspect --format '{{.ID}}' $IMAGE
57
    iid="$output"
58

59
    # Mount, and make sure the mount point exists
60
    run_podman image mount $IMAGE
61
    mount_path="$output"
62

63
    test -d $mount_path
64

65
    # Image is custom-built and has a file containing the YMD tag. Check it.
66
    testimage_file="/home/podman/testimage-id"
67
    test -e "$mount_path$testimage_file"
68
    is $(< "$mount_path$testimage_file") "$PODMAN_TEST_IMAGE_TAG"  \
69
       "Contents of $testimage_file in image"
70

71
    # 'image mount', no args, tells us what's mounted
72
    run_podman image mount
73
    is "$output" "$IMAGE *$mount_path" "podman image mount with no args"
74

75
    # Clean up, and make sure nothing is mounted any more
76
    run_podman image umount -f $IMAGE
77
    is "$output" "$iid" "podman image umount: image ID of what was umounted"
78

79
    run_podman image umount $IMAGE
80
    is "$output" "" "podman image umount: does not re-umount"
81

82
    run_podman 125 image umount no-such-image
83
    is "$output" "Error: no-such-image: image not known" \
84
       "error message from image umount no-such-image"
85

86
    # Tests for mount -a. This may mount more than one image! (E.g. systemd)
87
    run_podman image mount -a
88
    is "$output" "$IMAGE .*$mount_path"
89

90
    run_podman image umount -a
91
    assert "$output" =~ "$iid" "Test image is unmounted"
92

93
    run_podman image mount
94
    is "$output" "" "podman image mount, no args, after umount"
95
}
96

97
@test "podman run --mount ro=false " {
98
    local volpath=/path/in/container
99
    local stdopts="type=volume,destination=$volpath"
100

101
    # Variations on a theme (not by Paganini). All of these should fail.
102
    for varopt in readonly readonly=true ro=true ro rw=false;do
103
        run_podman 1 run --rm -q --mount $stdopts,$varopt $IMAGE touch $volpath/a
104
        is "$output" "touch: $volpath/a: Read-only file system" "with $varopt"
105
    done
106

107
    # All of these should pass
108
    for varopt in rw rw=true ro=false readonly=false;do
109
        run_podman run --rm -q --mount $stdopts,$varopt $IMAGE touch $volpath/a
110
    done
111
}
112

113
@test "podman run --mount image" {
114
    skip_if_rootless "too hard to test rootless"
115

116
    # Run a container with an image mount
117
    run_podman run --rm --mount type=image,src=$IMAGE,dst=/image-mount $IMAGE diff /etc/os-release /image-mount/etc/os-release
118

119
    # Make sure the mount is read-only
120
    run_podman 1 run --rm --mount type=image,src=$IMAGE,dst=/image-mount $IMAGE touch /image-mount/read-only
121
    is "$output" "touch: /image-mount/read-only: Read-only file system"
122

123
    # Make sure that rw,readwrite work
124
    run_podman run --rm --mount type=image,src=$IMAGE,dst=/image-mount,rw=true $IMAGE touch /image-mount/readwrite
125
    run_podman run --rm --mount type=image,src=$IMAGE,dst=/image-mount,readwrite=true $IMAGE touch /image-mount/readwrite
126

127
    skip_if_remote "mounting remote is meaningless"
128

129
    # The mount should be cleaned up during container removal as no other entity mounted the image
130
    run_podman image umount $IMAGE
131
    is "$output" "" "image mount should have been cleaned up during container removal"
132

133
    # Now make sure that the image mount is not cleaned up during container removal when another entity mounted the image
134
    run_podman image mount $IMAGE
135
    run_podman run --rm --mount type=image,src=$IMAGE,dst=/image-mount $IMAGE diff /etc/os-release /image-mount/etc/os-release
136

137
    run_podman image inspect --format '{{.ID}}' $IMAGE
138
    iid="$output"
139

140
    run_podman image umount $IMAGE
141
    is "$output" "$iid" "podman image umount: image ID of what was umounted"
142

143
    run_podman image umount $IMAGE
144
    is "$output" "" "image mount should have been cleaned up via 'image umount'"
145

146
    # Run a container in the background (source is the ID instead of name)
147
    run_podman run -d --mount type=image,src=$iid,dst=/image-mount,readwrite=true $IMAGE sleep infinity
148
    cid="$output"
149

150
    # Unmount the image
151
    run_podman image umount $IMAGE
152
    is "$output" "$iid" "podman image umount: image ID of what was umounted"
153
    run_podman image umount $IMAGE
154
    is "$output" "" "image mount should have been cleaned up via 'image umount'"
155

156
    # Make sure that the mount in the container is unaffected
157
    run_podman exec $cid diff /etc/os-release /image-mount/etc/os-release
158
    run_podman exec $cid find /image-mount/etc/
159

160
    # Clean up
161
    run_podman rm -t 0 -f $cid
162
}
163

164
@test "podman run --mount image inspection" {
165
    skip_if_rootless "too hard to test rootless"
166

167
    # Run a container in the background
168
    run_podman run -d --mount type=image,src=$IMAGE,dst=/image-mount,rw=true $IMAGE sleep infinity
169
    cid="$output"
170

171
    run_podman inspect --format "{{(index .Mounts 0).Type}}" $cid
172
    is "$output" "image" "inspect data includes image mount type"
173

174
    run_podman inspect --format "{{(index .Mounts 0).Source}}" $cid
175
    is "$output" "$IMAGE" "inspect data includes image mount source"
176

177
    run_podman inspect --format "{{(index .Mounts 0).Destination}}" $cid
178
    is "$output" "/image-mount" "inspect data includes image mount source"
179

180
    run_podman inspect --format "{{(index .Mounts 0).RW}}" $cid
181
    is "$output" "true" "inspect data includes image mount source"
182

183
    run_podman rm -t 0 -f $cid
184
}
185

186
@test "podman mount containers.conf" {
187
    skip_if_remote "remote does not support CONTAINERS_CONF*"
188

189
    dest=/$(random_string 30)
190
    tmpfile1=$PODMAN_TMPDIR/volume-test1
191
    random1=$(random_string 30)
192
    echo $random1 > $tmpfile1
193

194
    tmpfile2=$PODMAN_TMPDIR/volume-test2
195
    random2=$(random_string 30)
196
    echo $random2 > $tmpfile2
197
    bogus=$(random_string 10)
198

199
    mountStr1=type=bind,src=$tmpfile1,destination=$dest,ro,Z
200
    mountStr2=type=bind,src=$tmpfile2,destination=$dest,ro,Z
201
    containersconf=$PODMAN_TMPDIR/containers.conf
202
    cat >$containersconf <<EOF
203
[containers]
204
mounts=[ "$mountStr1", ]
205
EOF
206
    badcontainersconf=$PODMAN_TMPDIR/badcontainers.conf
207
    cat >$badcontainersconf <<EOF
208
[containers]
209
mounts=[ "type=$bogus,src=$tmpfile2,destination=$dest,ro", ]
210
EOF
211

212
    run_podman 1 run $IMAGE cat $dest
213
    is "$output" "cat: can't open '$dest': No such file or directory" "$dest does not exist"
214

215
    CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman run $IMAGE cat $dest
216
    is "$output" "$random1" "file should contain $random1"
217

218
    CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman run --mount $mountStr2 $IMAGE cat $dest
219
    is "$output" "$random2" "overridden file should contain $random2"
220

221
    CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman 125 run --mount $mountStr1 --mount $mountStr2 $IMAGE cat $dest
222
    is "$output" "Error: $dest: duplicate mount destination" "Should through duplicate destination error for $dest"
223

224
    CONTAINERS_CONF_OVERRIDE="$badcontainersconf" run_podman 125 run $IMAGE cat $dest
225
    is "$output" "Error: parsing containers.conf mounts: invalid filesystem type \"$bogus\"" "containers.conf should fail with bad mounts entry"
226

227
    run_podman rm --all --force -t 0
228
}
229

230
@test "podman mount external container - basic test" {
231
    # Only works with root (FIXME: does it work with rootless + vfs?)
232
    skip_if_rootless "mount does not work rootless"
233
    skip_if_remote "mounting remote is meaningless"
234

235
    # Create a container that podman does not know about
236
    external_cid=$(buildah from $IMAGE)
237

238
    run_podman mount $external_cid
239
    mount_path=$output
240

241
    # Test image will always have this file, and will always have the tag
242
    test -d $mount_path
243
    is $(< "$mount_path/home/podman/testimage-id") "$PODMAN_TEST_IMAGE_TAG"  \
244
       "Contents of well-known file in image"
245

246
    # Make sure that 'podman mount' (no args) returns the expected path
247
    run_podman mount --notruncate
248

249
    reported_mountpoint=$(echo "$output" | awk '{print $2}')
250
    is "$reported_mountpoint" "$mount_path" "mountpoint reported by 'podman mount'"
251

252
    # umount, and make sure mountpoint no longer exists
253
    run_podman umount $external_cid
254
    if findmnt "$mount_path" >/dev/null ; then
255
        die "'podman umount' did not umount $mount_path"
256
    fi
257
    buildah rm $external_cid
258
}
259

260
@test "podman volume globs" {
261
    v1a=v1_$(random_string)
262
    v1b=v1_$(random_string)
263
    v2=v2_$(random_string)
264
    vol1a=${PODMAN_TMPDIR}/$v1a
265
    vol1b=${PODMAN_TMPDIR}/$v1b
266
    vol2=${PODMAN_TMPDIR}/$v2
267
    touch $vol1a $vol1b $vol2
268

269
    # if volumes source and dest match then pass
270
    run_podman run --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,ro $IMAGE ls $vol1a $vol1b
271
    run_podman 1 run --rm --mount source=${PODMAN_TMPDIR}/v1\*,type=glob,ro $IMAGE ls $vol2
272
    is "$output" ".*No such file or directory" "$vol2 should not be mounted in the container"
273

274
    run_podman 125 run --rm --mount source=${PODMAN_TMPDIR}/v3\*,type=glob,ro $IMAGE ls $vol2
275
    is "$output" "Error: no file paths matching glob \"${PODMAN_TMPDIR}/v3\*\"" "Glob does not match so should throw error"
276

277
    run_podman 1 run --rm --mount source=${PODMAN_TMPDIR}/v2\*,type=glob,ro,Z $IMAGE touch $vol2
278
    is "$output" "touch: $vol2: Read-only file system" "Mount should be read-only"
279

280
    run_podman run --rm --mount source=${PODMAN_TMPDIR}/v2\*,type=glob,ro=false,Z $IMAGE touch $vol2
281

282
    run_podman run --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,destination=/non/existing/directory,ro $IMAGE ls /non/existing/directory
283
    is "$output" ".*$v1a" "podman images --inspect should include $v1a"
284
    is "$output" ".*$v1b" "podman images --inspect should include $v1b"
285

286
    run_podman create --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,ro $IMAGE ls $vol1a $vol1b
287
    cid=$output
288
    run_podman container inspect $output
289
    is "$output" ".*$vol1a" "podman images --inspect should include $vol1a"
290
    is "$output" ".*$vol1b" "podman images --inspect should include $vol1b"
291

292
    run_podman 125 run --rm --mount source=${PODMAN_TMPDIR}/v2\*,type=bind,ro=false $IMAGE touch $vol2
293
    is "$output" "Error: must set volume destination" "Bind mounts require destination"
294

295
    run_podman 125 run --rm --mount source=${PODMAN_TMPDIR}/v2\*,destination=/tmp/foobar, ro=false $IMAGE touch $vol2
296
    is "$output" "Error: invalid reference format" "Default mounts don not support globs"
297

298
    mkdir $PODMAN_TMPDIR/foo1 $PODMAN_TMPDIR/foo2 $PODMAN_TMPDIR/foo3
299
    touch $PODMAN_TMPDIR/foo1/bar $PODMAN_TMPDIR/foo2/bar $PODMAN_TMPDIR/foo3/bar
300
    touch $PODMAN_TMPDIR/foo1/bar1 $PODMAN_TMPDIR/foo2/bar2 $PODMAN_TMPDIR/foo3/bar3
301
    run_podman 125 run --rm --mount type=glob,source=${PODMAN_TMPDIR}/foo?/bar,destination=/tmp $IMAGE ls -l /tmp
302
    is "$output" "Error: /tmp/bar: duplicate mount destination" "Should report conflict on destination directory"
303
    run_podman run --rm --mount type=glob,source=${PODMAN_TMPDIR}/foo?/bar?,destination=/tmp,ro $IMAGE ls /tmp
304
    is "$output" "bar1.*bar2.*bar3" "Should match multiple source files on single destination directory"
305
}
306

307
@test "podman mount noswap memory mounts" {
308
    # tmpfs+noswap new in kernel 6.x, mid-2023; likely not in RHEL for a while
309
    if ! is_rootless; then
310
        testmount=$PODMAN_TMPDIR/testmount
311
        mkdir $testmount
312
        run mount -t tmpfs -o noswap none $testmount
313
        if [[ $status -ne 0 ]]; then
314
            if [[ $output =~ "bad option" ]]; then
315
                skip "requires kernel with tmpfs + noswap support"
316
            fi
317
            die "Could not test for tmpfs + noswap support: $output"
318
        else
319
            umount $testmount
320
        fi
321
    fi
322

323
    # if volumes source and dest match then pass
324
    run_podman run --rm --mount type=ramfs,destination=${PODMAN_TMPDIR} $IMAGE stat -f -c "%T" ${PODMAN_TMPDIR}
325
    is "$output" "ramfs" "ramfs mounted"
326

327
    if is_rootless; then
328
        run_podman 125 run --rm --mount type=tmpfs,destination=${PODMAN_TMPDIR},noswap  $IMAGE stat -f -c "%T" ${PODMAN_TMPDIR}
329
        is "$output" "Error: the 'noswap' option is only allowed with rootful tmpfs mounts: must provide an argument for option" "noswap not supported in rootless mode"
330
    else
331
        run_podman run --rm --mount type=tmpfs,destination=${PODMAN_TMPDIR},noswap  $IMAGE sh -c "mount| grep ${PODMAN_TMPDIR}"
332
        is "$output" ".*noswap" "tmpfs noswap mounted"
333
    fi
334
}
335

336
@test "podman mount no-dereference" {
337
    # Test how bind and glob-mounts behave with respect to relative (rel) and
338
    # absolute (abs) symlinks.
339

340
    if [ $(podman_runtime) != "crun" ]; then
341
        # Requires crun >= 1.11.0
342
        skip "only crun supports the no-dereference (copy-symlink) mount option"
343
    fi
344

345
    # Contents of the file 'data' inside the container image.
346
    declare -A datacontent=(
347
        [img]="data file inside the IMAGE - $(random_string 15)"
348
    )
349

350
    # Purpose of the image is so "link -> data" can point to an existing
351
    # file whether or not "data" is mounted.
352
    dockerfile=$PODMAN_TMPDIR/Dockerfile
353
    cat >$dockerfile <<EOF
354
FROM $IMAGE
355
RUN mkdir /mountroot && echo ${datacontent[img]} > /mountroot/data
356
EOF
357

358
    img="localhost/preserve:symlinks"
359
    run_podman build -t $img -f $dockerfile
360

361
    # Each test is set up in exactly the same way:
362
    #
363
    #    <tmpdir>/
364
    #    ├── mountdir/     <----- this is always the source dir
365
    #    │   ├── data
366
    #    │   └── link -> ?????
367
    #    └── otherdir/
368
    #        └── data
369
    #
370
    # The test is run in a container that has its own /mountroot/data file,
371
    # so in some situations 'link -> data' will get the container's
372
    # data file, in others it'll be the host's, and in others, ENOENT.
373
    #
374
    # There are four options for 'link': -> data in mountdir (same dir)
375
    # or otherdir, and, relative or absolute. Then, for each of those
376
    # permutations, run with and without no-dereference. (With no-dereference,
377
    # only the first of these options is valid, link->data. The other three
378
    # appear in the container as link->path-not-in-container)
379
    #
380
    # Finally, the table below defines a number of variations of mount
381
    # type (bind, glob); mount source (just the link, a glob, or entire
382
    # directory); and mount destination. These are the variations that
383
    # introduce complexity, hence the special cases in the innermost loop.
384
    #
385
    # Table format:
386
    #
387
    #    mount type | mount source | mount destination | what_is_data | enoents
388
    #
389
    # The what_is_data column indicates whether the file "data" in the
390
    # container will be the image's copy ("img") or the one from the host
391
    # ("in", referring to the source directory). "-" means N/A, no data file.
392
    #
393
    # The enoent column is a space-separated list of patterns to search for
394
    # in the test description. When these match, "link" will point to a
395
    # path that does not exist in the directory, and we should expect cat
396
    # to result in ENOENT.
397
    #
398
    tests="
399
bind | /link | /mountroot/link       | img
400
bind | /link | /i/do/not/exist/link  | -    | relative.*no-dereference
401
bind | /     | /mountroot/           | in   | absolute out
402
glob | /lin* | /mountroot/           | img
403
glob | /*    | /mountroot/           | in
404
"
405

406
    defer-assertion-failures
407

408
    while read mount_type mount_source mount_dest what_is_data enoents; do
409
        # link pointing inside the same directory, or outside
410
        for in_out in "in" "out"; do
411
            # relative symlink or absolute
412
            for rel_abs in "relative" "absolute"; do
413
                # Generate fresh new content for each data file (the in & out ones)
414
                datacontent[in]="data file in the SAME DIRECTORY - $(random_string 15)"
415
                datacontent[out]="data file OUTSIDE the tree - $(random_string 15)"
416

417
                # Populate data files in and out our tree
418
                local condition="${rel_abs:0:3}-${in_out}"
419
                local sourcedir="$PODMAN_TMPDIR/$condition"
420
                rm -rf $sourcedir $PODMAN_TMPDIR/outside-the-tree
421
                mkdir  $sourcedir $PODMAN_TMPDIR/outside-the-tree
422
                echo "${datacontent[in]}"  > "$sourcedir/data"
423
                echo "${datacontent[out]}" > "$PODMAN_TMPDIR/outside-the-tree/data"
424

425
                # Create the symlink itself (in the in-dir of course)
426
                local target
427
                case "$condition" in
428
                    rel-in)  target="data" ;;
429
                    rel-out) target="../outside-the-tree/data" ;;
430
                    abs-in)  target="$sourcedir/data" ;;
431
                    abs-out) target="$PODMAN_TMPDIR/outside-the-tree/data" ;;
432
                    *)       die "Internal error, invalid condition '$condition'" ;;
433
                esac
434
                ln -s $target "$sourcedir/link"
435

436
                # Absolute path to 'link' inside the container. What we stat & cat.
437
                local containerpath="$mount_dest"
438
                if [[ ! $containerpath =~ /link$ ]]; then
439
                    containerpath="${containerpath}link"
440
                fi
441

442
                # Now test with no args (mounts link CONTENT) and --no-dereference
443
                # (mounts symlink AS A SYMLINK)
444
                for mount_opts in "" ",no-dereference"; do
445
                    local description="$mount_type mount $mount_source -> $mount_dest ($in_out), $rel_abs $mount_opts"
446

447
                    # Expected exit status. Almost always success.
448
                    local exit_code=0
449

450
                    # Without --no-dereference, we always expect exactly the same,
451
                    # because podman mounts "link" as a data file...
452
                    local expect_stat="$containerpath"
453
                    local expect_cat="${datacontent[$in_out]}"
454
                    # ...except when bind-mounting link's parent directory: "link"
455
                    # is mounted as a link, and host's "data" file overrides the image
456
                    if [[ $mount_source = '/' ]]; then
457
                        expect_stat="'$containerpath' -> '$target'"
458
                    fi
459

460
                    # With --no-dereference...
461
                    if [[ -n "$mount_opts" ]]; then
462
                        # stat() is always the same (symlink and its target)  ....
463
                        expect_stat="'$containerpath' -> '$target'"
464

465
                        # ...and the only valid case for cat is same-dir relative:
466
                        if [[ "$condition" = "rel-in" ]]; then
467
                            expect_cat="${datacontent[$what_is_data]}"
468
                        else
469
                            # All others are ENOENT, because link -> nonexistent-path
470
                            exit_code=1
471
                        fi
472
                    fi
473

474
                    for ex in $enoents; do
475
                        if grep -q -w -E "$ex" <<<"$description"; then
476
                            exit_code=1
477
                        fi
478
                    done
479
                    if [[ $exit_code -eq 1 ]]; then
480
                        expect_cat="cat: can't open '$containerpath': No such file or directory"
481
                    fi
482

483
                    run_podman $exit_code run \
484
                               --mount type=$mount_type,src="$sourcedir$mount_source",dst="$mount_dest$mount_opts" \
485
                               --rm --privileged $img sh -c "stat -c '%N' $containerpath; cat $containerpath"
486
                    assert "${lines[0]}" = "$expect_stat" "$description -- stat $containerpath"
487
                    assert "${lines[1]}" = "$expect_cat"  "$description -- cat $containerpath"
488
                done
489
            done
490
        done
491
    done < <(parse_table "$tests")
492

493
    immediate-assertion-failures
494

495
    # Make sure that links are preserved across starts and stops
496
    local workdir=$PODMAN_TMPDIR/test-restart
497
    mkdir $workdir
498
    local datafile="data-$(random_string 5)"
499
    local datafile_contents="What we expect to see, $(random_string 20)"
500
    echo "$datafile_contents" > $workdir/$datafile
501
    ln -s $datafile $workdir/link
502

503
    run_podman create --mount type=glob,src=$workdir/*,dst=/mountroot/,no-dereference --privileged $img sh -c "stat -c '%N' /mountroot/link; cat /mountroot/link"
504
    cid="$output"
505
    run_podman start -a $cid
506
    assert "${lines[0]}" = "'/mountroot/link' -> '$datafile'" "symlink is preserved, on start"
507
    assert "${lines[1]}" = "$datafile_contents"         "glob matches symlink and host 'data' file, on start"
508
    run_podman start -a $cid
509
    assert "${lines[0]}" = "'/mountroot/link' -> '$datafile'" "symlink is preserved, on restart"
510
    assert "${lines[1]}" = "$datafile_contents"         "glob matches symlink and host 'data' file, on restart"
511
    run_podman rm -f -t=0 $cid
512

513
    run_podman rmi -f $img
514
}
515

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.