Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 118 additions & 3 deletions cmd/addref/add-ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@ package addref

import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/calypr/git-drs/internal/config"
"github.com/calypr/git-drs/internal/drslog"
"github.com/calypr/git-drs/internal/drsobject"
"github.com/calypr/git-drs/internal/gitrepo"
"github.com/calypr/git-drs/internal/lfs"
"github.com/calypr/git-drs/internal/remoteruntime"
drsapi "github.com/calypr/syfon/apigen/client/drs"
syclient "github.com/calypr/syfon/client"
"github.com/calypr/syfon/client/hash"
"github.com/spf13/cobra"
)

var remote string
var remoteType string
var Cmd = &cobra.Command{
Use: "add-ref <drs_uri> <dst path>",
Short: "Add a reference to an existing DRS object via URI",
Expand Down Expand Up @@ -43,7 +53,7 @@ var Cmd = &cobra.Command{
return err
}

obj, err := client.Client.DRS().GetObject(context.Background(), drsUri)
obj, err := resolveAddRefObject(context.Background(), cfg, remoteName, client, drsUri)
if err != nil {
return err
}
Expand All @@ -54,11 +64,116 @@ var Cmd = &cobra.Command{
os.MkdirAll(dirPath, os.ModePerm)
}

err = lfs.CreateLfsPointer(&obj, dstPath)
return err
oid := addRefLocalOID(drsUri, remoteName, &obj)
if hasContentSHA256(&obj) {
if err := lfs.CreateLfsPointerWithOID(&obj, dstPath, oid); err != nil {
return err
}
} else if err := lfs.CreateDRSPointer(&obj, dstPath, drsUri); err != nil {
return err
}
if obj.SelfUri == "" {
obj.SelfUri = drsUri
}
if err := drsobject.WriteObject(gitrepo.DRSObjectsPath, &obj, oid); err != nil {
return fmt.Errorf("write source DRS metadata: %w", err)
}
return nil
},
}

func init() {
Cmd.Flags().StringVarP(&remote, "remote", "r", "", "target remote DRS server (default: default_remote)")
Cmd.Flags().StringVar(&remoteType, "remote-type", "", "resolver remote type for DRS references (for example: terra)")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, you want the user not have to define this as a feature flag because that is not very scalable. Instead you use identifiers.org or something similar to use the drs compact id lookup to resolve the drs server.

}

func hasContentSHA256(obj *drsapi.DrsObject) bool {
return obj != nil && drsobject.NormalizeChecksum(hash.ConvertDrsChecksumsToHashInfo(obj.Checksums).SHA256) != ""
}

func addRefLocalOID(sourceURI string, remoteName config.Remote, obj *drsapi.DrsObject) string {
if obj != nil {
if sha := drsobject.NormalizeChecksum(hash.ConvertDrsChecksumsToHashInfo(obj.Checksums).SHA256); sha != "" {
return sha
}
}
return derivedSourceOID(sourceURI, string(remoteName))
}

func derivedSourceOID(sourceURI, remoteName string) string {
sum := sha256.Sum256([]byte("git-drs-source-ref:v1\nsource_uri=" + sourceURI + "\nremote=" + remoteName + "\n"))
return hex.EncodeToString(sum[:])
}

type drsObjectGetter interface {
GetObject(context.Context, string) (drsapi.DrsObject, error)
}

func resolveAddRefObject(ctx context.Context, cfg *config.Config, primaryRemote config.Remote, primary *remoteruntime.GitContext, drsURI string) (drsapi.DrsObject, error) {
objectID, sourceEndpoint, ok := parseDRSURIForSource(drsURI)
if !ok {
return primary.Client.DRS().GetObject(ctx, drsURI)
}
if sourceRemote, found := configuredRemoteForDRSURIHost(cfg, drsURI); found {
if sourceRemote == primaryRemote {
return primary.Client.DRS().GetObject(ctx, objectID)
}
client, err := remoteruntime.New(cfg, sourceRemote, primary.Logger)
if err != nil {
return drsapi.DrsObject{}, err
}
return client.Client.DRS().GetObject(ctx, objectID)
}
getter, err := newSourceDRSGetter(sourceEndpoint)
if err != nil {
return drsapi.DrsObject{}, err
}
return getter.GetObject(ctx, objectID)
}

func parseDRSURIForSource(drsURI string) (objectID string, endpoint string, ok bool) {
u, err := url.Parse(strings.TrimSpace(drsURI))
if err != nil || !strings.EqualFold(u.Scheme, "drs") || strings.TrimSpace(u.Host) == "" {
return "", "", false
}
objectID = strings.TrimPrefix(u.EscapedPath(), "/")
if objectID == "" {
return "", "", false
}
return objectID, "https://" + u.Host, true
}

func configuredRemoteForDRSURIHost(cfg *config.Config, drsURI string) (config.Remote, bool) {
u, err := url.Parse(strings.TrimSpace(drsURI))
if err != nil || !strings.EqualFold(u.Scheme, "drs") || strings.TrimSpace(u.Host) == "" {
return "", false
}
for name, selected := range cfg.Remotes {
remote := config.Config{Remotes: map[config.Remote]config.RemoteSelect{name: selected}}.GetRemote(name)
if remote == nil {
continue
}
endpoint, err := url.Parse(remote.GetEndpoint())
if err != nil {
continue
}
if strings.EqualFold(endpoint.Host, u.Host) {
return name, true
}
}
return "", false
}

var newSourceDRSGetter = newAnonymousSourceDRSGetter

func newAnonymousSourceDRSGetter(endpoint string) (drsObjectGetter, error) {
raw, err := syclient.New(endpoint)
if err != nil {
return nil, err
}
client, ok := raw.(*syclient.Client)
if !ok {
return nil, fmt.Errorf("unexpected syfon client type %T", raw)
}
return client.DRS(), nil
}
192 changes: 191 additions & 1 deletion cmd/addref/add-ref_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package addref

import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

"github.com/calypr/git-drs/internal/config"
"github.com/calypr/git-drs/internal/drslog"
"github.com/calypr/git-drs/internal/lfs"
"github.com/calypr/git-drs/internal/remoteruntime"
drsapi "github.com/calypr/syfon/apigen/client/drs"
)

func TestCreateLfsPointer(t *testing.T) {
obj := &drsapi.DrsObject{
Size: 10,
Checksums: []drsapi.Checksum{{Type: "sha256", Checksum: "abc"}},
Checksums: []drsapi.Checksum{{Type: "sha256", Checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}},
}
path := filepath.Join(t.TempDir(), "pointer")
if err := lfs.CreateLfsPointer(obj, path); err != nil {
Expand Down Expand Up @@ -40,3 +47,186 @@ func TestCreateLfsPointer_NoSHA256(t *testing.T) {
t.Fatalf("expected error for missing sha256")
}
}

func TestAddRefLocalOIDUsesDerivedSourceWhenSHA256Missing(t *testing.T) {
obj := &drsapi.DrsObject{Checksums: []drsapi.Checksum{{Type: "md5", Checksum: "md5"}}}
oid := addRefLocalOID("drs://example.org/object-1", "source", obj)
if len(oid) != 64 {
t.Fatalf("expected sha256-shaped derived oid, got %q", oid)
}
if oid != derivedSourceOID("drs://example.org/object-1", "source") {
t.Fatalf("expected derived source oid, got %s", oid)
}
}

func TestCreateDRSPointerPreservesSourceURI(t *testing.T) {
obj := &drsapi.DrsObject{Size: 42}
path := filepath.Join(t.TempDir(), "pointer")
if err := lfs.CreateDRSPointer(obj, path, "drs://example.org/object-1"); err != nil {
t.Fatalf("CreateDRSPointer error: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read pointer: %v", err)
}
expected := "version https://calypr.github.io/spec/v1\noid drs://example.org/object-1\nsize 42\n"
if string(data) != expected {
t.Fatalf("pointer mismatch: expected %q, got %q", expected, string(data))
}
}

func TestResolveAddRefObjectUsesSourceAuthorityRemoteCredentials(t *testing.T) {
var primaryRequests int
primary := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
primaryRequests++
http.Error(w, "primary remote must not resolve source DRS URI", http.StatusTeapot)
}))
defer primary.Close()

var sourceRequests int
source := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sourceRequests++
if r.URL.Path != "/ga4gh/drs/v1/objects/object-1" {
t.Fatalf("unexpected source path: %s", r.URL.Path)
}
user, pass, ok := r.BasicAuth()
if !ok || user != "source-user" || pass != "source-pass" {
t.Fatalf("expected source basic auth credentials, got ok=%v user=%q pass=%q", ok, user, pass)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"object-1","self_uri":"drs://` + r.Host + `/object-1","size":42}`))
}))
defer source.Close()

cfg := &config.Config{
DefaultRemote: "primary",
Remotes: map[config.Remote]config.RemoteSelect{
"primary": {Local: &config.LocalRemote{BaseURL: primary.URL}},
"source": {Local: &config.LocalRemote{BaseURL: source.URL, BasicUsername: "source-user", BasicPassword: "source-pass"}},
},
}
primaryCtx, err := remoteruntime.New(cfg, "primary", drslog.NewNoOpLogger())
if err != nil {
t.Fatalf("create primary runtime: %v", err)
}

obj, err := resolveAddRefObject(context.Background(), cfg, "primary", primaryCtx, "drs://"+strings.TrimPrefix(source.URL, "http://")+"/object-1")
if err != nil {
t.Fatalf("resolveAddRefObject: %v", err)
}
if obj.Id != "object-1" || obj.Size != 42 {
t.Fatalf("unexpected object: %+v", obj)
}
if sourceRequests != 1 {
t.Fatalf("expected one source request, got %d", sourceRequests)
}
if primaryRequests != 0 {
t.Fatalf("expected primary not to be contacted, got %d requests", primaryRequests)
}
}

func TestResolveAddRefObjectIgnoresPrimaryEvenWhenPrimaryCanResolve(t *testing.T) {
var primaryRequests int
primary := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
primaryRequests++
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"primary-object","self_uri":"drs://primary/object-1","size":99}`))
}))
defer primary.Close()

var sourceRequests int
source := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sourceRequests++
if r.URL.Path != "/ga4gh/drs/v1/objects/object-1" {
t.Fatalf("unexpected source path: %s", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"source-object","self_uri":"drs://` + r.Host + `/object-1","size":42}`))
}))
defer source.Close()

cfg := &config.Config{
DefaultRemote: "primary",
Remotes: map[config.Remote]config.RemoteSelect{
"primary": {Local: &config.LocalRemote{BaseURL: primary.URL}},
"source": {Local: &config.LocalRemote{BaseURL: source.URL}},
},
}
primaryCtx, err := remoteruntime.New(cfg, "primary", drslog.NewNoOpLogger())
if err != nil {
t.Fatalf("create primary runtime: %v", err)
}

obj, err := resolveAddRefObject(context.Background(), cfg, "primary", primaryCtx, "drs://"+strings.TrimPrefix(source.URL, "http://")+"/object-1")
if err != nil {
t.Fatalf("resolveAddRefObject: %v", err)
}
if obj.Id != "source-object" || obj.Size != 42 {
t.Fatalf("expected object from source DRS authority/resolver, got %+v", obj)
}
if sourceRequests != 1 {
t.Fatalf("expected one source request, got %d", sourceRequests)
}
if primaryRequests != 0 {
t.Fatalf("expected primary not to be contacted, got %d requests", primaryRequests)
}
}

type fakeDRSObjectGetter struct {
gotObjectID *string
obj drsapi.DrsObject
}

func (g fakeDRSObjectGetter) GetObject(_ context.Context, objectID string) (drsapi.DrsObject, error) {
*g.gotObjectID = objectID
return g.obj, nil
}

func TestResolveAddRefObjectUsesSourceAuthorityWhenNoRemoteMatches(t *testing.T) {
var primaryRequests int
primary := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
primaryRequests++
http.Error(w, "primary remote must not resolve source DRS URI", http.StatusTeapot)
}))
defer primary.Close()

cfg := &config.Config{
DefaultRemote: "primary",
Remotes: map[config.Remote]config.RemoteSelect{
"primary": {Local: &config.LocalRemote{BaseURL: primary.URL}},
},
}
primaryCtx, err := remoteruntime.New(cfg, "primary", drslog.NewNoOpLogger())
if err != nil {
t.Fatalf("create primary runtime: %v", err)
}

var gotEndpoint string
var gotObjectID string
oldGetter := newSourceDRSGetter
newSourceDRSGetter = func(endpoint string) (drsObjectGetter, error) {
gotEndpoint = endpoint
return fakeDRSObjectGetter{
gotObjectID: &gotObjectID,
obj: drsapi.DrsObject{Id: "source-object", Size: 42},
}, nil
}
defer func() { newSourceDRSGetter = oldGetter }()

obj, err := resolveAddRefObject(context.Background(), cfg, "primary", primaryCtx, "drs://source.example.org/object-1")
if err != nil {
t.Fatalf("resolveAddRefObject: %v", err)
}
if obj.Id != "source-object" || obj.Size != 42 {
t.Fatalf("expected object from source DRS authority/resolver, got %+v", obj)
}
if gotEndpoint != "https://source.example.org" {
t.Fatalf("expected source endpoint, got %q", gotEndpoint)
}
if gotObjectID != "object-1" {
t.Fatalf("expected source object ID, got %q", gotObjectID)
}
if primaryRequests != 0 {
t.Fatalf("expected primary not to be contacted, got %d requests", primaryRequests)
}
}
9 changes: 9 additions & 0 deletions cmd/addref/terra_addref_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package addref

import "testing"

func TestAcceptanceAddRefExposesRemoteTypeForTerraReferences(t *testing.T) {
if Cmd.Flags().Lookup("remote-type") == nil {
t.Fatalf("expected add-ref to expose --remote-type so Terra DRS references can select a Terra resolver explicitly")
Comment thread
bwalsh marked this conversation as resolved.
}
}
Loading
Loading