Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1658fa10ac |
+5
-5
@@ -72,11 +72,11 @@ RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
||||
# running, and exits 0 reporting `0 issues.` on a tree the real config
|
||||
# fails. Demonstrated on this repo at this pin, recorded on
|
||||
# https://git.eeqj.de/sneak/vaultik/pulls/114: with a planted
|
||||
# over-length line, `script/lint` exits 1 naming the `lll` finding with
|
||||
# `linters:` and exits 0 with `linterz:`. A set-but-ineffective config
|
||||
# quietly falling back to defaults is precisely the false-green class
|
||||
# this gate exists to eliminate, so it must not sit in the gate's own
|
||||
# configuration.
|
||||
# over-length line, `script/lint` exits 1 naming the `revive` finding
|
||||
# with `linters:` and exits 0 with `linterz:`. A set-but-ineffective
|
||||
# config quietly falling back to defaults is precisely the false-green
|
||||
# class this gate exists to eliminate, so it must not sit in the gate's
|
||||
# own configuration.
|
||||
#
|
||||
# `config verify` catches it, and it does so OFFLINE at this pinned
|
||||
# version -- verified, not assumed. Under `docker run --network none`
|
||||
|
||||
+178
-26
@@ -1,6 +1,8 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -252,29 +254,64 @@ func TestNoHostLintPathRemains(t *testing.T) {
|
||||
}
|
||||
|
||||
name := filepath.Join("script", entry.Name())
|
||||
for _, line := range shellCode(readRepoFile(t, name)) {
|
||||
|
||||
lines, err := shellCode(readRepoFile(t, name))
|
||||
require.NoError(t, err, "scanning %s", name)
|
||||
|
||||
for _, line := range lines {
|
||||
assertLinterIsContainerised(t, name, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assertLinterIsContainerised fails if the line runs the linter without
|
||||
// handing it to docker first. Position matters: docker has to come
|
||||
// before the binary, or the line is running the host linter and merely
|
||||
// mentioning docker afterwards.
|
||||
// assertLinterIsContainerised fails unless every command that names the
|
||||
// linter on this joined line is a docker command. Merely mentioning
|
||||
// docker somewhere on the line is not enough; see linterRunsInDocker.
|
||||
func assertLinterIsContainerised(t *testing.T, name, line string) {
|
||||
t.Helper()
|
||||
|
||||
at := strings.Index(line, linterBinary)
|
||||
if at < 0 {
|
||||
return
|
||||
assert.True(t, linterRunsInDocker(line),
|
||||
"%s runs %s outside a container; every command that names the"+
|
||||
" linter must begin with docker (line: %s)", name, linterBinary,
|
||||
line)
|
||||
}
|
||||
|
||||
docker := strings.Index(line, "docker")
|
||||
// linterRunsInDocker reports whether the linter, wherever it appears on
|
||||
// this joined shell line, is only ever the argument of a docker command.
|
||||
// The line is cut into the simple commands the shell would run -- on
|
||||
// `;`, `&&`, `||` and `|` -- and every command that names the linter
|
||||
// must begin with `docker`. This is what distinguishes the one
|
||||
// legitimate invocation, script/lint-fix's `docker run ... golangci-lint
|
||||
// run ...`, from evasions like `docker info; golangci-lint run` or
|
||||
// `docker info || golangci-lint run`, where the linter sits in a command
|
||||
// of its own that docker does not introduce.
|
||||
func linterRunsInDocker(line string) bool {
|
||||
for _, command := range splitShellCommands(line) {
|
||||
if !strings.Contains(command, linterBinary) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.True(t, docker >= 0 && docker < at,
|
||||
"%s runs %s on the host; every lint run happens in a container"+
|
||||
" (line: %s)", name, linterBinary, line)
|
||||
if !strings.HasPrefix(strings.TrimSpace(command), "docker") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// splitShellCommands breaks a joined shell line into the separate simple
|
||||
// commands the shell would run, cutting at the `;`, `&&`, `||` and `|`
|
||||
// operators (`||` before `|`, so the two-character operator is not split
|
||||
// twice). It is deliberately blind to quoting and to `$(...)`: no line
|
||||
// under guard puts one of these operators inside a string, and a scan
|
||||
// that tried to account for that would be the kind of half-parser this
|
||||
// file avoids.
|
||||
func splitShellCommands(line string) []string {
|
||||
for _, op := range []string{"&&", "||", "|", ";"} {
|
||||
line = strings.ReplaceAll(line, op, "\n")
|
||||
}
|
||||
|
||||
return strings.Split(line, "\n")
|
||||
}
|
||||
|
||||
// TestShellCodeSeesCodeAndNotProse keeps the scanner above honest. It
|
||||
@@ -287,20 +324,70 @@ func assertLinterIsContainerised(t *testing.T, name, line string) {
|
||||
func TestShellCodeSeesCodeAndNotProse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// A `<<` inside quotes is not a here-document, so the code after it
|
||||
// is still scanned; a real `<<EOF` opens one and its body is dropped.
|
||||
script := strings.Join([]string{
|
||||
"#!/bin/sh",
|
||||
"# a comment naming golangci-lint",
|
||||
"cat >&2 <<EOF",
|
||||
"prose naming golangci-lint, printed not executed",
|
||||
"EOF",
|
||||
`echo "a left shift << is not a here-document"`,
|
||||
"docker run --rm \\",
|
||||
" \"$image\" \\",
|
||||
" golangci-lint run ./...",
|
||||
}, "\n")
|
||||
|
||||
lines, err := shellCode(script)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
[]string{"cat >&2 <<EOF", `docker run --rm "$image" golangci-lint run ./...`},
|
||||
shellCode(script))
|
||||
[]string{
|
||||
"cat >&2 <<EOF",
|
||||
`echo "a left shift << is not a here-document"`,
|
||||
`docker run --rm "$image" golangci-lint run ./...`,
|
||||
},
|
||||
lines)
|
||||
|
||||
// A here-document still open at end of file must be a loud error,
|
||||
// not a silent truncation of everything the scanner has yet to see.
|
||||
unterminated := strings.Join([]string{
|
||||
"cat <<EOF",
|
||||
"body line naming golangci-lint, no terminator follows",
|
||||
}, "\n")
|
||||
|
||||
_, err = shellCode(unterminated)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// TestLinterCommandMustBeginWithDocker pins the property that a mention
|
||||
// of docker somewhere on the line is not enough: the command that
|
||||
// actually runs the linter has to be a docker command. The two evasions
|
||||
// from the issue place the linter in a command of its own, joined to a
|
||||
// harmless docker command by `;` or `||`; both must be rejected. The
|
||||
// containerised invocation script/lint-fix writes -- docker run with the
|
||||
// linter as its argument -- must still be accepted.
|
||||
func TestLinterCommandMustBeginWithDocker(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rejected := []string{
|
||||
"docker info >/dev/null; golangci-lint run ./...",
|
||||
"docker info || golangci-lint run ./...",
|
||||
"docker build . && golangci-lint run ./... | tee log",
|
||||
}
|
||||
for _, line := range rejected {
|
||||
assert.False(t, linterRunsInDocker(line),
|
||||
"a linter command docker does not introduce must be rejected: %s",
|
||||
line)
|
||||
}
|
||||
|
||||
accepted := []string{
|
||||
`docker run --rm "$image" golangci-lint run ./...`,
|
||||
`docker run --rm --user x --volume "$ROOT:/src" img golangci-lint run --fix ./...`,
|
||||
}
|
||||
for _, line := range accepted {
|
||||
assert.True(t, linterRunsInDocker(line),
|
||||
"a docker-introduced linter command must be accepted: %s", line)
|
||||
}
|
||||
}
|
||||
|
||||
// assertEpochExpandedInto fails unless some instruction runs the named
|
||||
@@ -410,7 +497,9 @@ func indexContaining(found []string, want string) int {
|
||||
// shellCode returns a POSIX shell script's executable lines: comments
|
||||
// dropped, here-document bodies dropped, and backslash continuations
|
||||
// joined so a multi-line command is a single string. Whitespace is
|
||||
// collapsed, as it is for Dockerfile instructions.
|
||||
// collapsed, as it is for Dockerfile instructions. A here-document left
|
||||
// open at end of file is an error rather than a silent truncation of
|
||||
// everything after its opener.
|
||||
//
|
||||
// Both exclusions are load-bearing rather than tidiness. The scripts
|
||||
// name golangci-lint in prose to state that the host binary is never
|
||||
@@ -418,7 +507,11 @@ func indexContaining(found []string, want string) int {
|
||||
// container invocation -- script/lint-fix's `docker run`, whose linter
|
||||
// command sits several lines below the word `docker` -- be recognised
|
||||
// as containerised.
|
||||
func shellCode(contents string) []string {
|
||||
//
|
||||
// This is a text scan, not a shell: it cannot see a linter name
|
||||
// assembled at runtime, one split across a continuation, a script in a
|
||||
// subdirectory of script/, or anything in the Makefile.
|
||||
func shellCode(contents string) ([]string, error) {
|
||||
var (
|
||||
out []string
|
||||
joined string
|
||||
@@ -452,23 +545,82 @@ func shellCode(contents string) []string {
|
||||
joined = ""
|
||||
}
|
||||
|
||||
return out
|
||||
if terminate != "" {
|
||||
return nil, fmt.Errorf("%w: terminator %q", errUnterminatedHeredoc,
|
||||
terminate)
|
||||
}
|
||||
|
||||
// heredocTerminator returns the terminator of the here-document a
|
||||
// command opens, or "" if it opens none. Only the first on a line is
|
||||
// recognised; nothing in script/ opens two.
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// errUnterminatedHeredoc is what shellCode returns when a here-document
|
||||
// is still open at end of file. Its callers require its absence, so an
|
||||
// unterminated body -- which would otherwise be swallowed silently --
|
||||
// fails the guard loudly.
|
||||
var errUnterminatedHeredoc = errors.New(
|
||||
"here-document opened but never closed before end of file")
|
||||
|
||||
// heredocTerminator returns the delimiter word of the here-document the
|
||||
// command opens, or "" if it opens none. A `<<` only opens one when it
|
||||
// is a real redirection: outside single and double quotes, and followed
|
||||
// by a delimiter word. A `<<` inside a quoted string, or an arithmetic
|
||||
// left shift like `$((x << 2))`, is not a here-document; the former is
|
||||
// the case this guards, the latter appears in no script here. Only the
|
||||
// first opener on a line is recognised; nothing in script/ opens two.
|
||||
func heredocTerminator(line string) string {
|
||||
_, after, opens := strings.Cut(line, "<<")
|
||||
if !opens {
|
||||
var quote byte // 0 when outside quotes, else '\'' or '"'
|
||||
|
||||
for i := 0; i+1 < len(line); i++ {
|
||||
c := line[i]
|
||||
|
||||
switch {
|
||||
case quote != 0:
|
||||
if c == quote {
|
||||
quote = 0
|
||||
}
|
||||
case c == '\'' || c == '"':
|
||||
quote = c
|
||||
case c == '<' && line[i+1] == '<':
|
||||
return heredocWord(line[i+2:])
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// `<<-` strips leading tabs from the body; the terminator word is
|
||||
// the same either way, and callers compare against trimmed lines.
|
||||
word, _, _ := strings.Cut(strings.TrimPrefix(after, "-"), " ")
|
||||
// heredocWord extracts the delimiter that follows `<<` or `<<-`: it drops
|
||||
// an optional `-`, skips blanks, then reads the delimiter -- quoted or
|
||||
// bare -- and returns it with quotes removed. `<<-'EOF'` and `<< EOF`
|
||||
// both yield "EOF". It returns "" when no word follows, so a bare `<<`
|
||||
// opens nothing.
|
||||
func heredocWord(after string) string {
|
||||
after = strings.TrimLeft(strings.TrimPrefix(after, "-"), " \t")
|
||||
|
||||
return strings.Trim(word, `'"`)
|
||||
var (
|
||||
word strings.Builder
|
||||
quote byte
|
||||
)
|
||||
|
||||
for i := range len(after) {
|
||||
c := after[i]
|
||||
|
||||
switch {
|
||||
case quote != 0:
|
||||
if c == quote {
|
||||
quote = 0
|
||||
} else {
|
||||
word.WriteByte(c)
|
||||
}
|
||||
case c == '\'' || c == '"':
|
||||
quote = c
|
||||
case c == ' ' || c == '\t':
|
||||
return word.String()
|
||||
default:
|
||||
word.WriteByte(c)
|
||||
}
|
||||
}
|
||||
|
||||
return word.String()
|
||||
}
|
||||
|
||||
// readRepoFile reads a file by its path relative to the repository
|
||||
|
||||
+1
-30
@@ -1,7 +1,6 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -25,11 +24,6 @@ const configSetArgs = 2
|
||||
// parent config dirs (e.g. ~/.config) are conventionally traversable.
|
||||
const configDirMode = 0o755
|
||||
|
||||
// configYAMLIndent matches the 2-space indentation of defaultConfigTemplate,
|
||||
// so `config set` writes the file back with the same indentation rather than
|
||||
// yaml.Marshal's 4-space default.
|
||||
const configYAMLIndent = 2
|
||||
|
||||
var (
|
||||
errConfigExists = errors.New("config file already exists")
|
||||
errEmptyConfig = errors.New("empty config file")
|
||||
@@ -387,7 +381,7 @@ Examples:
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := marshalConfigYAML(root)
|
||||
out, err := yaml.Marshal(root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling config: %w", err)
|
||||
}
|
||||
@@ -411,29 +405,6 @@ Examples:
|
||||
}
|
||||
}
|
||||
|
||||
// marshalConfigYAML renders a config document tree with 2-space indentation,
|
||||
// matching defaultConfigTemplate. yaml.Marshal defaults to 4 spaces, which
|
||||
// would reindent the whole file on the first `config set` despite the promise
|
||||
// to preserve formatting.
|
||||
func marshalConfigYAML(root *yaml.Node) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
enc := yaml.NewEncoder(&buf)
|
||||
enc.SetIndent(configYAMLIndent)
|
||||
|
||||
err := enc.Encode(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = enc.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// loadYAMLFile parses a YAML file into a yaml.Node document tree,
|
||||
// which preserves comments and ordering for round-tripping.
|
||||
func loadYAMLFile(path string) (*yaml.Node, error) {
|
||||
|
||||
@@ -188,47 +188,6 @@ func TestYAMLPathSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigSetPreservesFormatting asserts the `config set` write path
|
||||
// (marshalConfigYAML) round-trips a 2-space-indented file without reindenting
|
||||
// it to yaml.Marshal's 4-space default, and keeps comments.
|
||||
func TestConfigSetPreservesFormatting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := parseTestYAML(t)
|
||||
|
||||
err := yamlPathSet(root, splitPath("s3.bucket"), "newbucket")
|
||||
if err != nil {
|
||||
t.Fatalf("set s3.bucket: %v", err)
|
||||
}
|
||||
|
||||
out, err := marshalConfigYAML(root)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
|
||||
text := string(out)
|
||||
|
||||
for _, want := range []string{"# top comment", "# inline comment"} {
|
||||
if !contains(text, want) {
|
||||
t.Errorf("round-tripped YAML dropped comment %q:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
|
||||
// Nested map keys stay at 2-space indent; the bug reindented them to 4.
|
||||
if !contains(text, "\n bucket: newbucket") {
|
||||
t.Errorf("expected 2-space indent for s3.bucket, got:\n%s", text)
|
||||
}
|
||||
|
||||
if contains(text, "\n bucket:") {
|
||||
t.Errorf("s3.bucket reindented to 4 spaces:\n%s", text)
|
||||
}
|
||||
|
||||
// Sequence items under a key also stay at 2 spaces.
|
||||
if !contains(text, "\n - age1aaa") {
|
||||
t.Errorf("expected 2-space indent for sequence item, got:\n%s", text)
|
||||
}
|
||||
}
|
||||
|
||||
func splitPath(s string) []string {
|
||||
return strings.Split(s, ".")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package cli
|
||||
|
||||
import "time"
|
||||
|
||||
// SnapshotInfo represents snapshot information for listing
|
||||
//
|
||||
//nolint:tagliatelle // snake_case is the established output format
|
||||
type SnapshotInfo struct {
|
||||
ID string `json:"id"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
CompressedSize int64 `json:"compressed_size"`
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Package models defines shared value types describing files, chunks,
|
||||
// blobs, and snapshots as they move through the backup pipeline.
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// FileInfo represents a file in the backup system
|
||||
type FileInfo struct {
|
||||
Path string
|
||||
MTime time.Time
|
||||
Size int64
|
||||
}
|
||||
|
||||
// ChunkInfo represents a content-addressed chunk
|
||||
type ChunkInfo struct {
|
||||
Hash string // SHA256 hash
|
||||
Size int64
|
||||
Offset int64 // Offset within source file
|
||||
}
|
||||
|
||||
// ChunkRef represents a reference to a chunk in a blob or file
|
||||
type ChunkRef struct {
|
||||
ChunkHash string
|
||||
Offset int64
|
||||
Length int64
|
||||
}
|
||||
|
||||
// BlobInfo represents an encrypted blob containing multiple chunks
|
||||
type BlobInfo struct {
|
||||
Hash string // SHA256 hash of the blob content (content-addressable)
|
||||
CreatedAt time.Time
|
||||
Size int64
|
||||
ChunkCount int
|
||||
}
|
||||
|
||||
// Snapshot represents a backup snapshot
|
||||
type Snapshot struct {
|
||||
ID string // ISO8601 timestamp
|
||||
Hostname string
|
||||
Version string
|
||||
CreatedAt time.Time
|
||||
FileCount int64
|
||||
ChunkCount int64
|
||||
BlobCount int64
|
||||
TotalSize int64
|
||||
MetadataSize int64
|
||||
}
|
||||
|
||||
// SnapshotMetadata contains the full metadata for a snapshot
|
||||
type SnapshotMetadata struct {
|
||||
Snapshot *Snapshot
|
||||
Files map[string]*FileInfo
|
||||
Chunks map[string]*ChunkInfo
|
||||
Blobs map[string]*BlobInfo
|
||||
FileChunks map[string][]*ChunkRef // path -> chunks
|
||||
BlobChunks map[string][]*ChunkRef // blob hash -> chunks
|
||||
}
|
||||
|
||||
// Chunk represents a data chunk for processing
|
||||
type Chunk struct {
|
||||
Data []byte
|
||||
Hash string
|
||||
Offset int64
|
||||
Length int64
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/vaultik/internal/models"
|
||||
)
|
||||
|
||||
// TestModelsCompilation ensures all model types can be instantiated
|
||||
func TestModelsCompilation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// This test primarily serves as a compilation test
|
||||
// to ensure all types are properly defined
|
||||
|
||||
// Test FileInfo
|
||||
fi := &models.FileInfo{
|
||||
Path: "/test/file.txt",
|
||||
MTime: time.Now(),
|
||||
Size: 1024,
|
||||
}
|
||||
if fi.Path != "/test/file.txt" {
|
||||
t.Errorf("FileInfo.Path not set correctly")
|
||||
}
|
||||
|
||||
// Test ChunkInfo
|
||||
ci := &models.ChunkInfo{
|
||||
Hash: "abc123",
|
||||
Size: 512,
|
||||
Offset: 0,
|
||||
}
|
||||
if ci.Hash != "abc123" {
|
||||
t.Errorf("ChunkInfo.Hash not set correctly")
|
||||
}
|
||||
|
||||
// Test BlobInfo
|
||||
bi := &models.BlobInfo{
|
||||
Hash: "blob123",
|
||||
CreatedAt: time.Now(),
|
||||
Size: 1024,
|
||||
ChunkCount: 2,
|
||||
}
|
||||
if bi.Hash != "blob123" {
|
||||
t.Errorf("BlobInfo.Hash not set correctly")
|
||||
}
|
||||
|
||||
// Test Snapshot
|
||||
s := &models.Snapshot{
|
||||
ID: "2024-01-01T00:00:00Z",
|
||||
Hostname: "test-host",
|
||||
Version: "1.0.0",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if s.ID != "2024-01-01T00:00:00Z" {
|
||||
t.Errorf("Snapshot.ID not set correctly")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Vaultik test configuration
|
||||
hostname: test-host
|
||||
index_path: /tmp/vaultik-test/index.db
|
||||
source_dirs:
|
||||
- /tmp/vaultik-test/source
|
||||
|
||||
# S3 configuration
|
||||
s3:
|
||||
endpoint: http://localhost:19000 # gofakes3 test endpoint
|
||||
bucket: test-bucket
|
||||
prefix: test-
|
||||
access_key_id: test-key
|
||||
secret_access_key: test-secret
|
||||
region: us-east-1
|
||||
|
||||
# Chunking configuration
|
||||
chunk_size: 65536 # 64KB average chunk size
|
||||
min_chunk_size: 32768 # 32KB minimum
|
||||
max_chunk_size: 131072 # 128KB maximum
|
||||
blob_size: 1048576 # 1MB blobs for testing
|
||||
|
||||
# Compression
|
||||
compression_level: 3
|
||||
|
||||
# Encryption
|
||||
# age_recipients:
|
||||
# - age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3mw88h
|
||||
@@ -0,0 +1,24 @@
|
||||
age_recipients:
|
||||
- age1278m9q7dp3chsh2dcy82qk27v047zywyvtxwnj4cvt0z65jw6a7q5dqhfj # sneak's long term age key
|
||||
- age1ezrjmfpwsc95svdg0y54mums3zevgzu0x0ecq2f7tp8a05gl0sjq9q9wjg # insecure integration test key
|
||||
source_dirs:
|
||||
- /tmp/vaultik-test-source
|
||||
exclude:
|
||||
- '*.log'
|
||||
- '*.tmp'
|
||||
- '.git'
|
||||
- 'node_modules'
|
||||
s3:
|
||||
endpoint: http://ber1app1.local:3900/
|
||||
bucket: vaultik-integration-test
|
||||
prefix: test-host/
|
||||
access_key_id: GKbc8e6d35fdf50847f155aca5
|
||||
secret_access_key: 217046bee47c050301e3cc13e3cba1a8a943cf5f37f8c7979c349c5254441d18
|
||||
region: us-east-1
|
||||
use_ssl: false
|
||||
part_size: 5242880 # 5MB
|
||||
index_path: /tmp/vaultik-integration-test.sqlite
|
||||
chunk_size: 10MB
|
||||
blob_size_limit: 10GB
|
||||
compression_level: 3
|
||||
hostname: test-host
|
||||
Reference in New Issue
Block a user