Skip to content
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

sda-admin cli #1026

Merged
merged 33 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f6ecb3b
implement general usage and command sytax
nanjiangshu Sep 3, 2024
4d5908c
implement list command
nanjiangshu Sep 3, 2024
f3f24cf
add helping functions
nanjiangshu Sep 3, 2024
8d285c4
implement file commands
nanjiangshu Sep 3, 2024
7bed167
implement dataset commands
nanjiangshu Sep 3, 2024
4af1f08
add usage instructions
nanjiangshu Sep 3, 2024
e1ae956
improve help message handling
nanjiangshu Sep 3, 2024
ec71cb4
user flag is mandatory for the list files command
nanjiangshu Sep 4, 2024
ccfd3b9
do not use underscore in variable name
nanjiangshu Sep 4, 2024
9003edc
remove irrelevant unit tests suggested by jocke
nanjiangshu Sep 4, 2024
8c181cb
parse url properly and remove prefix in function names
nanjiangshu Sep 4, 2024
42c85f9
make sure all return statements have a blank line before
nanjiangshu Sep 4, 2024
f1242e1
sanitize filepath
nanjiangshu Sep 4, 2024
a3247fe
update readme for listing files
nanjiangshu Sep 4, 2024
5a66fa4
fix unit tests after renaming functions
nanjiangshu Sep 5, 2024
42146c7
solve double slash problem in concatenated url using path.Join
nanjiangshu Sep 5, 2024
529644e
implement commets from Malin
nanjiangshu Sep 10, 2024
c3bfe4b
refactor: exit only at the main function
nanjiangshu Sep 10, 2024
ac6219f
refactor: use consistent <none> <verb> commands structure
nanjiangshu Sep 10, 2024
b76c6b2
add sda-admin unit tests to the workflow
nanjiangshu Sep 10, 2024
82a8fbc
improve README
nanjiangshu Sep 12, 2024
6ed71f8
print all error message in the main function
nanjiangshu Sep 12, 2024
7052542
add sda-admin to code-linter
nanjiangshu Sep 12, 2024
08805ed
add build, lint and test functionalities in the Makefile
nanjiangshu Sep 12, 2024
c7d5d31
fix linting problem
nanjiangshu Sep 12, 2024
744d35b
do not check token on the cli side
nanjiangshu Sep 12, 2024
af5855a
show response from API when server returned non 200 code
nanjiangshu Sep 12, 2024
96bbad6
Better logic and remove unnecessary output
nanjiangshu Sep 14, 2024
86a6060
Decapitalize error message
nanjiangshu Sep 14, 2024
2a90be2
improved url concatenation to handle when datasetID is URL like
nanjiangshu Sep 14, 2024
e2dc242
Improved usage message and the logic of outputing to stdout and stderr
nanjiangshu Sep 16, 2024
ae67160
refactor: remove non useful components
nanjiangshu Sep 17, 2024
95c6bf9
pass nil as the jsonBody for the dataset release function
nanjiangshu Sep 18, 2024
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
25 changes: 24 additions & 1 deletion .github/workflows/code-linter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,27 @@ jobs:
uses: golangci/[email protected]
with:
args: -E bodyclose,gocritic,gofmt,gosec,govet,nestif,nlreturn,rowserrcheck -e G401,G501,G107,G115 --timeout 5m
working-directory: sda
working-directory: sda

lint_sda_admin:
name: Lint sda-admin code
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go-version: ['1.22']
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v4

- name: Run golangci-lint
uses: golangci/[email protected]
with:
args: -E bodyclose,gocritic,gofmt,gosec,govet,nestif,nlreturn,rowserrcheck -e G401,G501,G107,G115 --timeout 5m
working-directory: sda-admin
40 changes: 39 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:

- name: Get dependencies
run: |
cd sda-download
cd sda
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
Expand All @@ -104,3 +104,41 @@ jobs:
file: ./sda/coverage.txt
flags: unittests
fail_ci_if_error: false

test_sda_admin:
name: Test SDA Admin
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.22']
steps:

- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v4

- name: Get dependencies
run: |
cd sda-admin
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Test
run: |
cd sda-admin
go test -v -coverprofile=coverage.txt -covermode=atomic ./...

- name: Codecov
uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./sda-admin/coverage.txt
flags: unittests
fail_ci_if_error: false
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ bootstrap: go-version-check docker-version-check
GO111MODULE=off go get golang.org/x/tools/cmd/goimports

# build containers
build-all: build-postgresql build-rabbitmq build-sda build-sda-download build-sda-sftp-inbox
build-all: build-postgresql build-rabbitmq build-sda build-sda-download build-sda-sftp-inbox build-sda-admin
build-postgresql:
@cd postgresql && docker build -t ghcr.io/neicnordic/sensitive-data-archive:PR$$(date +%F)-postgres .
build-rabbitmq:
Expand All @@ -34,6 +34,8 @@ build-sda-download:
@cd sda-download && docker build -t ghcr.io/neicnordic/sensitive-data-archive:PR$$(date +%F)-download .
build-sda-sftp-inbox:
@cd sda-sftp-inbox && docker build -t ghcr.io/neicnordic/sensitive-data-archive:PR$$(date +%F)-sftp-inbox .
build-sda-admin:
@cd sda-admin && go build


go-version-check: SHELL:=/bin/bash
Expand Down Expand Up @@ -81,13 +83,16 @@ integrationtest-sda: build-all
@PR_NUMBER=$$(date +%F) docker compose -f .github/integration/sda-posix-integration.yml down -v --remove-orphans

# lint go code
lint-all: lint-sda lint-sda-download
lint-all: lint-sda lint-sda-download lint-sda-admin
lint-sda:
@echo 'Running golangci-lint in the `sda` folder'
@cd sda && golangci-lint run $(LINT_INCLUDE) $(LINT_EXCLUDE)
lint-sda-download:
@echo 'Running golangci-lint in the `sda-download` folder'
@cd sda-download && golangci-lint run $(LINT_INCLUDE) $(LINT_EXCLUDE)
lint-sda-admin:
@echo 'Running golangci-lint in the `sda-admin` folder'
@cd sda-admin && golangci-lint run $(LINT_INCLUDE) $(LINT_EXCLUDE)

# run static code tests
test-all: test-sda test-sda-download test-sda-sftp-inbox
Expand All @@ -97,3 +102,5 @@ test-sda-download:
@cd sda-download && go test ./... -count=1
test-sda-sftp-inbox:
@docker run --rm -v ./sda-sftp-inbox:/inbox maven:3.9.4-eclipse-temurin-21-alpine sh -c "cd /inbox && mvn test -B"
test-sda-admin:
@cd sda-admin && go test ./... -count=1
97 changes: 97 additions & 0 deletions sda-admin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# sda-admin

`sda-admin` is a command-line tool for managing sensitive data archives. It provides functionalities to list users and files, ingest and set accession IDs for files, and create or release datasets.

## General Usage

```sh
sda-admin [-uri URI] [-token TOKEN] <command> [options]
```

## Global Options
- `-uri URI`
Set the URI for the API server (optional if the environmental variable `API_HOST` is set).
- `-token TOKEN`
Set the authentication token (optional if the environmental variable `ACCESS_TOKEN` is set).

## List all users

Use the following command to return all users with active uploads
```sh
sda-admin user list
```

## List all files for a specified user

Use the following command to return all files belonging to the specified user `[email protected]`
```sh
sda-admin file list -user [email protected]
```

## Ingest a file

Use the following command to trigger the ingesting of a given file `/path/to/file.c4gh` that belongs to the user `[email protected]`

```sh
sda-admin file ingest -filepath /path/to/file.c4gh -user [email protected]
```

## Assign an accession ID to a file

Use the following command to assign an accession ID `my-accession-id-1` to a given file `/path/to/file.c4gh` that belongs to the user `[email protected]`

```sh
sda-admin file set-accession -filepath /path/to/file.c4gh -user [email protected] -accession-id my-accession-id-1
```

## Create a dataset from a list of accession IDs and a dataset ID

Use the following command to create a dataset `dataset001` from accession IDs `my-accession-id-1` and `my-accession-id-2`

```sh
sda-admin dataset create -dataset-id dataset001 my-accession-id-1 my-accession-id-2
```


## Release a dataset for downloading

Use the following command to release the dataset `dataset001` for downloading

```sh
sda-admin dataset release -dataset-id dataset001
```

## Show version information

Use the following command to show the version information for sda-admin.

```sh
sda-admin version
```

## Help

For detailed usage information about specific commands or options, use:

```sh
sda-admin help <command>
```

### Examples

To get help on the `file` command:
```sh
sda-admin help file
```

To get help on the `file ingest` command:

```sh
sda-admin help file ingest
```

To get help on the `dataset create` command:

```sh
sda-admin help dataset create
```
57 changes: 57 additions & 0 deletions sda-admin/dataset/dataset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dataset

import (
"encoding/json"
"fmt"
"net/url"
"path"

"github.com/neicnordic/sensitive-data-archive/sda-admin/helpers"
)

type RequestBodyDataset struct {
AccessionIDs []string `json:"accession_ids"`
DatasetID string `json:"dataset_id"`
}

// Create creates a dataset from a list of accession IDs and a dataset ID.
func Create(apiURI, token, datasetID string, accessionIDs []string) error {
parsedURL, err := url.Parse(apiURI)
if err != nil {
return err
}
parsedURL.Path = path.Join(parsedURL.Path, "dataset/create")

requestBody := RequestBodyDataset{
AccessionIDs: accessionIDs,
DatasetID: datasetID,
}

jsonBody, err := json.Marshal(requestBody)
if err != nil {
return fmt.Errorf("failed to marshal JSON, reason: %v", err)
}

_, err = helpers.PostRequest(parsedURL.String(), token, jsonBody)
if err != nil {
return err
}

return nil
}

// Release releases a dataset for downloading
func Release(apiURI, token, datasetID string) error {
parsedURL, err := url.Parse(apiURI)
if err != nil {
return err
}
parsedURL.Path = path.Join(parsedURL.Path, "dataset/release") + "/" + datasetID

_, err = helpers.PostRequest(parsedURL.String(), token, nil)
if err != nil {
return err
}

return nil
nanjiangshu marked this conversation as resolved.
Show resolved Hide resolved
}
93 changes: 93 additions & 0 deletions sda-admin/dataset/dataset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package dataset

import (
"errors"
"testing"

"github.com/neicnordic/sensitive-data-archive/sda-admin/helpers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

// MockHelpers is a mock implementation of the helpers package functions
type MockHelpers struct {
mock.Mock
}

func (m *MockHelpers) PostRequest(url, token string, jsonBody []byte) ([]byte, error) {
args := m.Called(url, token, jsonBody)

return args.Get(0).([]byte), args.Error(1)
}

func TestCreate_Success(t *testing.T) {
mockHelpers := new(MockHelpers)
originalFunc := helpers.PostRequest
helpers.PostRequest = mockHelpers.PostRequest
defer func() { helpers.PostRequest = originalFunc }() // Restore original after test

expectedURL := "http://example.com/dataset/create"
token := "test-token"
datasetID := "dataset-123"
accessionIDs := []string{"accession-1", "accession-2"}
jsonBody := []byte(`{"accession_ids":["accession-1","accession-2"],"dataset_id":"dataset-123"}`)

mockHelpers.On("PostRequest", expectedURL, token, jsonBody).Return([]byte(`{}`), nil)

err := Create("http://example.com", token, datasetID, accessionIDs)
assert.NoError(t, err)
mockHelpers.AssertExpectations(t)
}

func TestCreate_PostRequestFailure(t *testing.T) {
mockHelpers := new(MockHelpers)
originalFunc := helpers.PostRequest
helpers.PostRequest = mockHelpers.PostRequest
defer func() { helpers.PostRequest = originalFunc }() // Restore original after test

expectedURL := "http://example.com/dataset/create"
token := "test-token"
datasetID := "dataset-123"
accessionIDs := []string{"accession-1", "accession-2"}
jsonBody := []byte(`{"accession_ids":["accession-1","accession-2"],"dataset_id":"dataset-123"}`)

mockHelpers.On("PostRequest", expectedURL, token, jsonBody).Return([]byte(nil), errors.New("failed to send request"))

err := Create("http://example.com", token, datasetID, accessionIDs)
assert.Error(t, err)
assert.EqualError(t, err, "failed to send request")
mockHelpers.AssertExpectations(t)
}

func TestRelease_Success(t *testing.T) {
mockHelpers := new(MockHelpers)
originalFunc := helpers.PostRequest
helpers.PostRequest = mockHelpers.PostRequest
defer func() { helpers.PostRequest = originalFunc }() // Restore original after test

expectedURL := "http://example.com/dataset/release/dataset-123"
token := "test-token"

mockHelpers.On("PostRequest", expectedURL, token, []byte(nil)).Return([]byte(`{}`), nil)

err := Release("http://example.com", token, "dataset-123")
assert.NoError(t, err)
mockHelpers.AssertExpectations(t)
}

func TestRelease_PostRequestFailure(t *testing.T) {
mockHelpers := new(MockHelpers)
originalFunc := helpers.PostRequest
helpers.PostRequest = mockHelpers.PostRequest
defer func() { helpers.PostRequest = originalFunc }() // Restore original after test

expectedURL := "http://example.com/dataset/release/dataset-123"
token := "test-token"

mockHelpers.On("PostRequest", expectedURL, token, []byte(nil)).Return([]byte(nil), errors.New("failed to send request"))

err := Release("http://example.com", token, "dataset-123")
assert.Error(t, err)
assert.EqualError(t, err, "failed to send request")
mockHelpers.AssertExpectations(t)
}
Loading
Loading