Skip to content

feature/documentation #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
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
9 changes: 9 additions & 0 deletions .drsconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"queryServer": {
"baseURL": "https://calypr.ohsu.edu/ga4gh"
},
"writeServer": {
"baseURL": "https://calypr.ohsu.edu/ga4gh"
},
"gen3Profile": "<calypr-prod-profile-name-here>"
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/
.DS_Store
/tmp
14 changes: 14 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Git DRS Client

## Getting Started

1. Configure gen3 with your credentials ([docs](https://aced-idp.github.io/requirements/#1-download-gen3-client))
2. Edit platform URL and gen3 profile in `.drsconfig`
3. Build from source
```bash
go build
```
4. Access through command line
```bash
./git-drs --help
```
59 changes: 59 additions & 0 deletions client/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package client

import (
"io"
"log"
"os"
"path/filepath"

"github.com/bmeg/git-drs/utils"
"sigs.k8s.io/yaml"
)

type Server struct {
BaseURL string `json:"baseURL"`
ExtensionType string `json:"type,omitempty"`
}

type Config struct {
QueryServer Server `json:"queryServer"`
WriteServer Server `json:"writeServer"`
Gen3Profile string `json:"gen3Profile"`
}

const (
DRS_CONFIG = ".drsconfig"
)

func LoadConfig() (*Config, error) {
//look in Git base dir and find .drsconfig file

topLevel, err := utils.GitTopLevel()

if err != nil {
return nil, err
}

configPath := filepath.Join(topLevel, DRS_CONFIG)

log.Printf("Looking for %s", configPath)
//check if config exists
reader, err := os.Open(configPath)
if err != nil {
return nil, err
}

b, err := io.ReadAll(reader)
if err != nil {
return nil, err
}

conf := Config{}
err = yaml.Unmarshal(b, &conf)
if err != nil {
return nil, err
}

log.Printf("Config: %s %#v", string(b), conf)
return &conf, nil
}
166 changes: 166 additions & 0 deletions client/indexd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package client

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"

"github.com/bmeg/git-drs/drs"
"github.com/uc-cdis/gen3-client/gen3-client/jwt"
)

var conf jwt.Configure
var profileConfig jwt.Credential

type IndexDClient struct {
base *url.URL
profile string
}

func NewIndexDClient(base string) (ObjectStoreClient, error) {
baseURL, err := url.Parse(base)
// print baseURL
if err != nil {
return nil, err
}

cfg, err := LoadConfig()
if err != nil {
return nil, err
}

// get the gen3Profile from the config
profile := cfg.Gen3Profile
if profile == "" {
return nil, fmt.Errorf("No gen3 profile specified. Please provide a gen3Profile key in your .drsconfig")
}

fmt.Printf("Base URL: %s\n", baseURL.String())
fmt.Printf("Profile: %s\n", profile)

return &IndexDClient{baseURL, profile}, err
}

// DownloadFile implements ObjectStoreClient
func (cl *IndexDClient) DownloadFile(id string, access_id string, dstPath string) (*drs.AccessURL, error) {
// get file from indexd
a := *cl.base
a.Path = filepath.Join(a.Path, "drs/v1/objects", id, "access", access_id)
// a.Path = filepath.Join("https://calypr.ohsu.edu/user/data/download/", id)

// unmarshal response
req, err := http.NewRequest("GET", a.String(), nil)
if err != nil {
return nil, err
}
// extract accessToken from gen3 profile and insert into header of request
profileConfig = conf.ParseConfig(cl.profile)
if profileConfig.AccessToken == "" {
return nil, fmt.Errorf("access token not found in profile config")
}

// Add headers to the request
authStr := "Bearer " + profileConfig.AccessToken
req.Header.Set("Authorization", authStr)

client := &http.Client{}
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()

body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}

out := drs.AccessURL{}
err = json.Unmarshal(body, &out)
if err != nil {
return nil, err
}

// Extract the signed URL from the response
signedURL := out.URL
if signedURL == "" {
return nil, fmt.Errorf("signed URL not found in response.")
}

// Download the file using the signed URL
fileResponse, err := http.Get(signedURL)
if err != nil {
return nil, err
}
defer fileResponse.Body.Close()

// Check if the response status is OK
if fileResponse.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to download file using signed URL: %s", fileResponse.Status)
}

// Create the destination directory if it doesn't exist
err = os.MkdirAll(filepath.Dir(dstPath), os.ModePerm)
if err != nil {
return nil, err
}

// Create the destination file
dstFile, err := os.Create(dstPath)
if err != nil {
return nil, err
}
defer dstFile.Close()

// Write the file content to the destination file
_, err = io.Copy(dstFile, fileResponse.Body)
if err != nil {
return nil, err
}

fmt.Printf("File written to %s\n", dstFile.Name())

return &out, nil
}

// RegisterFile implements ObjectStoreClient.
func (cl *IndexDClient) RegisterFile(path string, name string) (*drs.DRSObject, error) {
panic("unimplemented")
}

func (cl *IndexDClient) QueryID(id string) (*drs.DRSObject, error) {

a := *cl.base
a.Path = filepath.Join(a.Path, "drs/v1/objects", id)

req, err := http.NewRequest("GET", a.String(), nil)
if err != nil {
return nil, err
}
// Add headers to the request
req.Header.Set("Authorization", "Bearer <your-token>")
req.Header.Set("Custom-Header", "HeaderValue")

client := &http.Client{}
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()

body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}

out := drs.DRSObject{}
err = json.Unmarshal(body, &out)
if err != nil {
return nil, err
}
return &out, nil
}
14 changes: 14 additions & 0 deletions client/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package client

import "github.com/bmeg/git-drs/drs"

type ObjectStoreClient interface {
//Given a DRS string ID, retrieve the object describing it
QueryID(id string) (*drs.DRSObject, error)

//Put file into object storage and obtain a DRS record pointing to it
RegisterFile(path string, name string) (*drs.DRSObject, error)

//Download file given a DRS ID
DownloadFile(id string, access_id string, dstPath string) (*drs.AccessURL, error)
}
27 changes: 27 additions & 0 deletions cmd/add/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package add

import (
"fmt"
"path/filepath"

"github.com/spf13/cobra"
)

// Cmd line declaration
var Cmd = &cobra.Command{
Use: "add",
Short: "Add a file",
Long: ``,
Args: cobra.MinimumNArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
for _, fileArg := range args {
matches, err := filepath.Glob(fileArg)
if err == nil {
for _, f := range matches {
fmt.Printf("Adding %s\n", f)
}
}
}
return nil
},
}
57 changes: 57 additions & 0 deletions cmd/download/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package download

import (
"github.com/bmeg/git-drs/client"
"github.com/bmeg/git-drs/drs"
"github.com/spf13/cobra"
)

var (
server string
dstPath string
drsObj *drs.DRSObject
)

// Cmd line declaration
// Cmd line declaration
var Cmd = &cobra.Command{
Use: "download <drsId> <accessId>",
Short: "Download file using DRS ID and access ID",
Long: "Download file using DRS ID and access ID. The access ID is the access method used to download the file.",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
drsId := args[0]
accessId := args[1]
cfg, err := client.LoadConfig()
if err != nil {
return err
}

baseURL := cfg.QueryServer.BaseURL

client, err := client.NewIndexDClient(baseURL)
if err != nil {
return err
}

if dstPath == "" {

drsObj, err = client.QueryID(drsId)
if err != nil {
return err
}
dstPath = drsObj.Name
}

_, err = client.DownloadFile(drsId, accessId, dstPath)
if err != nil {
return err
}

return nil
},
}

func init() {
Cmd.Flags().StringVarP(&dstPath, "dstPath", "d", "", "Optional destination file path")
}
Loading