Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0185e20
fix url
matthewpeterkort Oct 16, 2025
b1226e1
adds empty command. Backwards compatible with existing etl job
matthewpeterkort Oct 16, 2025
eab4a8d
fix up research study gen
matthewpeterkort Oct 30, 2025
44befa4
bump data client version
matthewpeterkort Nov 3, 2025
5f8d009
add explorer config validation
matthewpeterkort Nov 10, 2025
8d6ca8a
add config init, sower API support
matthewpeterkort Nov 12, 2025
ff15e7f
fix up sower resp, instructions
matthewpeterkort Nov 12, 2025
8cc3a18
update sower to work with latest git-drs
matthewpeterkort Dec 1, 2025
d9f37d1
cleanup uuid gen
matthewpeterkort Dec 4, 2025
de1c80e
upgrade deps
matthewpeterkort Dec 5, 2025
358d1fb
upgrade deps
matthewpeterkort Dec 30, 2025
84cc9ed
Bugfix/git drs updates (#11)
quinnwai Jan 8, 2026
ecdbaef
lovely day
quinnwai Jan 9, 2026
b9ffacd
bonk
quinnwai Jan 9, 2026
8a45b54
refactor validate to use paths
quinnwai Jan 9, 2026
7777f99
Merge pull request #12 from calypr/feature/docs
lbeckman314 Jan 21, 2026
ec1c28c
update deps, factor out sower client into data-client
matthewpeterkort Jan 30, 2026
3110dd3
fix build errs
matthewpeterkort Jan 30, 2026
49debd7
update deps
matthewpeterkort Feb 11, 2026
84f9f4f
update deps
matthewpeterkort Feb 11, 2026
7ec1622
fix go mod file
matthewpeterkort Feb 11, 2026
9ece214
fix bug where directories overwrite eachother because uuids are not u…
matthewpeterkort Feb 12, 2026
266ecf9
update forge to support github only uploads
matthewpeterkort Feb 23, 2026
68f6004
debug gh only records
matthewpeterkort Feb 23, 2026
a19eefa
clean up metadata creation logic
matthewpeterkort Feb 23, 2026
e537c5c
make forge a bit smarter
matthewpeterkort Feb 24, 2026
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