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
89 changes: 61 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,80 @@
# Meta
# Forge

Metadata handling for CALPYR data platform
FHIR metadata management for CALYPR Gen3 data repositories.

## Workflow -- General Design Paramters
Forge works alongside [git-drs](https://github.com/calypr/git-drs/blob/main/README.md) to generate and publish FHIR-compliant metadata, making your datasets discoverable on the CALYPR platform.

This repo is designed to produce git hook commands that take care of metadata additions / subtractions that are run before or after certain git commands like commit and push. Draft workflow currently:
## Quick Start

## Example user workflow
```bash
# Verify your connection to CALYPR
forge ping

```
git clone repo
forge init -- exactly same as git-dirs init, just a wrapper around it
git add files
git commit -m "test" -- same as git-drs
git push origin main -- same as git-dirs
forge publish [github personal access token]
# Publish metadata to CALYPR
forge publish ghp_your_github_token

# Monitor the job
forge list
forge status <job-uid>
```

To generate a personal access token for a github repo check these docs:
https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
## What Forge Does

## Command descriptions
**1. Manage Project Metadata**
- `forge publish` - Generate and upload metadata to CALYPR
- `forge empty` - Remove project metadata
- `forge meta` - Preview metadata locally
- `forge validate` - Check metadata validity

### ping
**2. Monitor Platform State**
- `forge ping` - Check connection and credentials
- `forge list` - View all processing jobs
- `forge status` - Check specific job status
- `forge output` - View job logs

Same as ping in g3t
**3. Configure Portal Frontend**
- `forge config` - Generate a CALYPR explorer template

### meta
## Installation

```bash
git clone https://github.com/calypr/forge.git
cd forge
go build -o forge
sudo mv forge /usr/local/bin/
```

Generates metadata from non checked in .meta files. If .meta files are already checked in you can regen metadata with -r flag. This command is run as part of the pre-commit command
## Prerequisites

### validate
- Git DRS installed and configured
- Data files pushed to CALYPR via git-drs
- Gen3 credentials (configured through git-drs)
- GitHub Personal Access Token ([create token](https://github.com/settings/tokens))

Validates metadata against the jsonschema in grip
## Documentation

### precommit
- [Getting Started](docs/getting-started.md) - Setup and basic workflows
- [Command Reference](docs/commands.md) - Detailed command documentation
- [Configuration Guide](docs/configuration.md) - Git-drs configuration
- [Metadata Structure](docs/metadata.md) - Understanding FHIR resources

Runs meta init command then locates all .ndjson files in META directory and validates each file.
## Example Workflow

### publish
```bash
# Use git-drs to track and push files
git lfs track "*.fastq.gz"
git add data/sample.fastq.gz
git commit -m "Add sequencing data"
git push

# Publish metadata to CALYPR
forge publish ghp_abc123def456

# Monitor the job
forge list
# Uid: job-xyz789 Name: fhir_import_export Status: Succeeded
```

Validates that your Personal Access token exists and is valid
Packages together relevent information used to init the git repo in a remote job
Kicks off a sower job to process the metadata files that you have just pushed up
## Support

No git hook for publish, users are expected to run that themselves.
Part of the CALYPR data commons ecosystem.
110 changes: 10 additions & 100 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,116 +1,26 @@
package client

import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"path/filepath"
"time"

token "github.com/bmeg/grip-graphql/middleware"
"github.com/calypr/git-drs/client"
drsConfig "github.com/calypr/git-drs/config"

"github.com/calypr/data-client/client/commonUtils"
"github.com/calypr/data-client/client/jwt"
"github.com/calypr/data-client/g3client"
"github.com/calypr/data-client/logs"
)

type Gen3Client struct {
Base *url.URL
Cred jwt.Credential
ProjectId string
BucketName string
}

// load repo-level config and return a new IndexDClient
func NewGen3Client() (*Gen3Client, error) {
var conf jwt.Configure

// NewGen3Client loads repo-level config and return a new DRSClient
func NewGen3Client(remote drsConfig.Remote, opts ...g3client.Option) (client.DRSClient, func(), error) {
cfg, err := drsConfig.LoadConfig()
if err != nil {
return nil, err
}

profile := cfg.Servers.Gen3.Auth.Profile
if profile == "" {
return nil, fmt.Errorf("No gen3 profile specified. Please provide a gen3Profile key in your .drsconfig")
}

cred, err := conf.ParseConfig(profile)
if err != nil {
return nil, err
}

baseUrl, err := url.Parse(cred.APIEndpoint)
if err != nil {
return nil, fmt.Errorf("error parsing base URL from profile %s: %v", profile, err)
}

// get the gen3Project and gen3Bucket from the config
projectId := cfg.Servers.Gen3.Auth.ProjectID
if projectId == "" {
return nil, fmt.Errorf("No gen3 project specified. Please provide a gen3Project key in your .drsconfig")
}

bucketName := cfg.Servers.Gen3.Auth.Bucket
if bucketName == "" {
return nil, fmt.Errorf("No gen3 bucket specified. Please provide a gen3Bucket key in your .drsconfig")
}

return &Gen3Client{Base: baseUrl, Cred: cred, ProjectId: projectId, BucketName: bucketName}, err
}

type Resp struct {
Body []byte
Err error
}

func (cl *Gen3Client) MakeReq(method string, path string, body []byte) *Resp {
a := *cl.Base
a.Path = filepath.Join(a.Path, path)

var reqBodyReader io.Reader
if body != nil {
reqBodyReader = bytes.NewBuffer(body)
}

req, err := http.NewRequest(method, a.String(), reqBodyReader)
if err != nil {
return &Resp{nil, err}
return nil, nil, err
}
expiration, err := token.GetExpiration(cl.Cred.AccessToken)
if err != nil {
return &Resp{nil, err}
}
// Update AccessToken if token is old
if expiration.Before(time.Now()) {
r := jwt.Request{}
err := r.RequestNewAccessToken(cl.Base.String()+commonUtils.FenceAccessTokenEndpoint, &cl.Cred)
if err != nil {
return &Resp{nil, err}
}
}
if cl.Cred.AccessToken == "" {
return &Resp{nil, fmt.Errorf("access token not found in profile config")}
}
req.Header.Set("Authorization", "bearer "+cl.Cred.AccessToken)

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

if response.StatusCode != http.StatusOK {
return &Resp{nil, fmt.Errorf("failed to check authz, response body: %v", response)}
}
logger, closer := logs.New(string(remote))

RespBody, err := io.ReadAll(response.Body)
dClient, err := cfg.GetRemoteClient(remote, logger.Logger, opts...)
if err != nil {
return &Resp{nil, fmt.Errorf("failed to read response Body")}
return nil, closer, err
}

return &Resp{RespBody, nil}
return dClient, closer, nil
}
75 changes: 0 additions & 75 deletions client/fence/fence.go

This file was deleted.

85 changes: 0 additions & 85 deletions client/fence/resp.go

This file was deleted.

Loading