diff --git a/copy.go b/copy.go index 2fbcc66..ca62909 100644 --- a/copy.go +++ b/copy.go @@ -2,43 +2,12 @@ package nix import ( "context" - "fmt" "iter" "os" ) -const ( - EnvAwsSharedCredentialsFile = "AWS_SHARED_CREDENTIALS_FILE" -) - -// A BinaryCache holds credentials and parameters to a s3 binary cache. -type BinaryCache struct { - // Compression is the name of the compression algorithm to use. Example: "zstd". - Compression string `json:"compression"` - // ParallelCompression determines whether parallel compression is enabled. - ParallelCompression bool `json:"parallel_compression,omitempty"` - - // Bucket is the s3 bucket name. - Bucket string `json:"bucket"` - // Endpoint is the s3 endpoint. Example: "s3.example.org". - Endpoint string `json:"endpoint,omitempty"` - // Region is the s3 region. Example: "ap-northeast-1". - Region string `json:"region"` - // Scheme is the s3 protocol. Example: "https". - Scheme string `json:"scheme"` - // CredentialsPath is the path to the s3 shared credentials file. - CredentialsPath string `json:"credentials_path"` -} - -func (store *BinaryCache) String() string { - return fmt.Sprintf( - "s3://%s?compression=%s¶llel-compression=%t®ion=%s&scheme=%s&endpoint=%s", - store.Bucket, store.Compression, store.ParallelCompression, store.Region, store.Scheme, store.Endpoint, - ) -} - -// Copy copies installables to the binary cache store, signing all paths using the key at keyPath. -func Copy(ctx Context, keyPath string, store *BinaryCache, installables iter.Seq[string]) error { +// Copy copies installables to the binary cache store. +func Copy(ctx Context, store Store, installables iter.Seq[string]) error { if store == nil { return os.ErrInvalid } @@ -47,9 +16,9 @@ func Copy(ctx Context, keyPath string, store *BinaryCache, installables iter.Seq defer cancel() cmd := ctx.Nix(c, CommandCopy, - FlagTo, store.String()+"&secret-key="+keyPath, + FlagTo, store.String(), FlagStdin) - cmd.Env = append(os.Environ(), EnvAwsSharedCredentialsFile+"="+store.CredentialsPath) + cmd.Env = append(os.Environ(), store.Environ()...) cmd.Stdout, cmd.Stderr = ctx.Streams() _, err := ctx.WriteStdin(cmd, installables, nil) diff --git a/copy_test.go b/copy_test.go index 0f920c7..873a082 100644 --- a/copy_test.go +++ b/copy_test.go @@ -36,46 +36,10 @@ func init() { }) } -func TestBinaryCache(t *testing.T) { - testCases := []struct { - name string - store *nix.BinaryCache - want string - }{ - {"example", &nix.BinaryCache{ - Compression: "none", - ParallelCompression: false, - Bucket: "example", - Endpoint: "s3.example.org", - Region: "us-east-1", - Scheme: "http", - CredentialsPath: "/dev/null", - }, "s3://example?compression=none¶llel-compression=false®ion=us-east-1&scheme=http&endpoint=s3.example.org"}, - - {"gensokyo", &nix.BinaryCache{ - Compression: "zstd", - ParallelCompression: true, - Bucket: "nix-cache", - Endpoint: "s3.gensokyo.uk", - Region: "ap-northeast-1", - Scheme: "https", - CredentialsPath: "/var/lib/persist/cache/s3", - }, "s3://nix-cache?compression=zstd¶llel-compression=true®ion=ap-northeast-1&scheme=https&endpoint=s3.gensokyo.uk"}, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if got := tc.store.String(); got != tc.want { - t.Errorf("String: %q, want %q", got, tc.want) - } - }) - } -} - func TestCopy(t *testing.T) { stubNixCommand(t) if err := nix.Copy( newStubContext(t.Context(), nil, os.Stdout, os.Stderr), - nonexistent, &nix.BinaryCache{ Compression: "none", ParallelCompression: false, @@ -84,6 +48,7 @@ func TestCopy(t *testing.T) { Region: "us-east-1", Scheme: "http", CredentialsPath: "/dev/null", + KeyPath: nonexistent, }, slices.Values(instWant["pluiedev pappardelle"]), ); err != nil { @@ -93,7 +58,6 @@ func TestCopy(t *testing.T) { t.Run("nil store", func(t *testing.T) { if err := nix.Copy( newStubContext(t.Context(), nil, os.Stdout, os.Stderr), - nonexistent, nil, nil, ); !errors.Is(err, os.ErrInvalid) { diff --git a/store.go b/store.go new file mode 100644 index 0000000..2f1ea41 --- /dev/null +++ b/store.go @@ -0,0 +1,50 @@ +package nix + +import ( + "fmt" + "strings" +) + +type Store interface { + // Environ returns extra environment variables specified by Store. + Environ() []string + + fmt.Stringer +} + +const ( + EnvAwsSharedCredentialsFile = "AWS_SHARED_CREDENTIALS_FILE" +) + +// A BinaryCache holds credentials and parameters to a s3 binary cache. +type BinaryCache struct { + // Compression is the name of the compression algorithm to use. Example: "zstd". + Compression string `json:"compression"` + // ParallelCompression determines whether parallel compression is enabled. + ParallelCompression bool `json:"parallel_compression,omitempty"` + + // Bucket is the s3 bucket name. + Bucket string `json:"bucket"` + // Endpoint is the s3 endpoint. Example: "s3.example.org". + Endpoint string `json:"endpoint,omitempty"` + // Region is the s3 region. Example: "ap-northeast-1". + Region string `json:"region"` + // Scheme is the s3 protocol. Example: "https". + Scheme string `json:"scheme"` + // CredentialsPath is the path to the s3 shared credentials file. + CredentialsPath string `json:"credentials_path"` + + // KeyPath is the path to the nix secret key for signing all newly copied paths. + KeyPath string `json:"key_path"` +} + +func (store *BinaryCache) Environ() []string { + return []string{EnvAwsSharedCredentialsFile + "=" + strings.TrimSpace(store.CredentialsPath)} +} + +func (store *BinaryCache) String() string { + return fmt.Sprintf( + "s3://%s?compression=%s¶llel-compression=%t®ion=%s&scheme=%s&endpoint=%s&secret-key=%s", + store.Bucket, store.Compression, store.ParallelCompression, store.Region, store.Scheme, store.Endpoint, store.KeyPath, + ) +} diff --git a/store_test.go b/store_test.go new file mode 100644 index 0000000..eb5b64f --- /dev/null +++ b/store_test.go @@ -0,0 +1,47 @@ +package nix_test + +import ( + "testing" + + "gensokyo.uk/nix" +) + +func TestBinaryCache(t *testing.T) { + testCases := []struct { + name string + store *nix.BinaryCache + want string + wantEnv []string + }{ + {"example", &nix.BinaryCache{ + Compression: "none", + ParallelCompression: false, + Bucket: "example", + Endpoint: "s3.example.org", + Region: "us-east-1", + Scheme: "http", + CredentialsPath: "/dev/null", + KeyPath: nonexistent, + }, "s3://example?compression=none¶llel-compression=false®ion=us-east-1&scheme=http&endpoint=s3.example.org&secret-key=/proc/nonexistent", + []string{nix.EnvAwsSharedCredentialsFile + "=/dev/null"}}, + + {"gensokyo", &nix.BinaryCache{ + Compression: "zstd", + ParallelCompression: true, + Bucket: "nix-cache", + Endpoint: "s3.gensokyo.uk", + Region: "ap-northeast-1", + Scheme: "https", + CredentialsPath: "/var/lib/persist/cache/s3", + KeyPath: "/var/lib/persist/cache/key", + }, "s3://nix-cache?compression=zstd¶llel-compression=true®ion=ap-northeast-1&scheme=https&endpoint=s3.gensokyo.uk&secret-key=/var/lib/persist/cache/key", + []string{nix.EnvAwsSharedCredentialsFile + "=/var/lib/persist/cache/s3"}}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if got := tc.store.String(); got != tc.want { + t.Errorf("String: %q, want %q", got, tc.want) + } + }) + } +}