init
This commit is contained in:
commit
929cd70212
|
@ -0,0 +1,9 @@
|
|||
.vscode/
|
||||
idea/
|
||||
.DS_Store
|
||||
vendor/
|
||||
_output/
|
||||
web/build/
|
||||
web/node_modules/
|
||||
web/.eslintcache
|
||||
mysql-data
|
|
@ -0,0 +1,17 @@
|
|||
_output
|
||||
.idea
|
||||
*.log
|
||||
tags
|
||||
*.swp
|
||||
**/*.pem
|
||||
**/*.csr
|
||||
*.db
|
||||
womtool.jar
|
||||
mysql-data
|
||||
*.sqlite3
|
||||
.vscode
|
||||
|
||||
web/build/
|
||||
web/node_modules/
|
||||
web/.eslintcache
|
||||
**/*.DS_Store
|
|
@ -0,0 +1,230 @@
|
|||
# Options for analysis running.
|
||||
run:
|
||||
# The default concurrency value is the number of available CPU.
|
||||
concurrency: 4
|
||||
# Timeout for analysis, e.g. 30s, 5m.
|
||||
# Default: 1m
|
||||
timeout: 5m
|
||||
# Exit code when at least one issue was found.
|
||||
# Default: 1
|
||||
issues-exit-code: 2
|
||||
# Include test files or not.
|
||||
# Default: true
|
||||
tests: false
|
||||
# List of build tags, all linters use it.
|
||||
# Default: [].
|
||||
build-tags:
|
||||
- mytag
|
||||
# Which dirs to skip: issues from them won't be reported.
|
||||
# Can use regexp here: `generated.*`, regexp is applied on full path,
|
||||
# including the path prefix if one is set.
|
||||
# Default value is empty list,
|
||||
# but default dirs are skipped independently of this option's value (see skip-dirs-use-default).
|
||||
# "/" will be replaced by current OS file path separator to properly work on Windows.
|
||||
skip-dirs:
|
||||
- src/external_libs
|
||||
- autogenerated_by_my_lib
|
||||
# Enables skipping of directories:
|
||||
# - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
# Default: true
|
||||
skip-dirs-use-default: false
|
||||
# Which files to skip: they will be analyzed, but issues from them won't be reported.
|
||||
# Default value is empty list,
|
||||
# but there is no need to include all autogenerated files,
|
||||
# we confidently recognize autogenerated files.
|
||||
# If it's not please let us know.
|
||||
# "/" will be replaced by current OS file path separator to properly work on Windows.
|
||||
skip-files:
|
||||
- ".*\\.my\\.go$"
|
||||
- lib/bad.go
|
||||
# If set we pass it to "go list -mod={option}". From "go help modules":
|
||||
# If invoked with -mod=readonly, the go command is disallowed from the implicit
|
||||
# automatic updating of go.mod described above. Instead, it fails when any changes
|
||||
# to go.mod are needed. This setting is most useful to check that go.mod does
|
||||
# not need updates, such as in a continuous integration and testing system.
|
||||
# If invoked with -mod=vendor, the go command assumes that the vendor
|
||||
# directory holds the correct copies of dependencies and ignores
|
||||
# the dependency descriptions in go.mod.
|
||||
#
|
||||
# Allowed values: readonly|vendor|mod
|
||||
# By default, it isn't set.
|
||||
modules-download-mode: readonly
|
||||
# Allow multiple parallel golangci-lint instances running.
|
||||
# If false (default) - golangci-lint acquires file lock on start.
|
||||
allow-parallel-runners: false
|
||||
# Define the Go version limit.
|
||||
# Mainly related to generics support since go1.18.
|
||||
# Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.18
|
||||
go: '1.19'
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
# Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions|teamcity
|
||||
#
|
||||
# Multiple can be specified by separating them by comma, output can be provided
|
||||
# for each of them by separating format name and path by colon symbol.
|
||||
# Output path can be either `stdout`, `stderr` or path to the file to write to.
|
||||
# Example: "checkstyle:report.xml,json:stdout,colored-line-number"
|
||||
#
|
||||
# Default: colored-line-number
|
||||
format: colored-line-number
|
||||
# Print lines of code with issue.
|
||||
# Default: true
|
||||
print-issued-lines: false
|
||||
# Print linter name in the end of issue text.
|
||||
# Default: true
|
||||
print-linter-name: false
|
||||
# Make issues output unique by line.
|
||||
# Default: true
|
||||
uniq-by-line: false
|
||||
# Add a prefix to the output file references.
|
||||
# Default is no prefix.
|
||||
path-prefix: ""
|
||||
# Sort results by: filepath, line and column.
|
||||
sort-results: false
|
||||
|
||||
linters-settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: exported
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments:
|
||||
- "checkPrivateReceivers"
|
||||
- "sayRepetitiveInsteadOfStutters"
|
||||
- "disableStutteringCheck"
|
||||
|
||||
goimports:
|
||||
# A comma-separated list of prefixes, which, if set, checks import paths
|
||||
# with the given prefixes are grouped after 3rd-party packages.
|
||||
# Default: ""
|
||||
local-prefixes: "github.com/Bio-OS/bioos"
|
||||
|
||||
linters:
|
||||
# Disable all linters.
|
||||
# Default: false
|
||||
disable-all: true
|
||||
# Enable specific linter
|
||||
# https://golangci-lint.run/usage/linters/#enabled-by-default
|
||||
enable:
|
||||
- ineffassign
|
||||
- goimports
|
||||
- govet
|
||||
- revive
|
||||
- unused
|
||||
|
||||
# Run only fast linters from enabled linters set (first run won't be fast)
|
||||
# Default: false
|
||||
fast: false
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude.
|
||||
#
|
||||
# But independently of this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`.
|
||||
# To list all excluded by default patterns execute `golangci-lint run --help`
|
||||
#
|
||||
# Default: https://golangci-lint.run/usage/false-positives/#default-exclusions
|
||||
exclude:
|
||||
- test/.*
|
||||
- third_party/.*
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
# Exclude known linters from partially hard-vendored code,
|
||||
# which is impossible to exclude via `nolint` comments.
|
||||
# `/` will be replaced by current OS file path separator to properly work on Windows.
|
||||
- path: internal/hmac/
|
||||
text: "weak cryptographic primitive"
|
||||
linters:
|
||||
- gosec
|
||||
# Exclude some `staticcheck` messages.
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA9003:"
|
||||
# Exclude `lll` issues for long lines with `go:generate`.
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
# Independently of option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option.
|
||||
# To list all excluded by default patterns execute `golangci-lint run --help`.
|
||||
# Default: true.
|
||||
exclude-use-default: true
|
||||
# If set to true exclude and exclude-rules regular expressions become case-sensitive.
|
||||
# Default: false
|
||||
exclude-case-sensitive: false
|
||||
# The list of ids of default excludes to include or disable.
|
||||
# https://golangci-lint.run/usage/false-positives/#default-exclusions
|
||||
# Default: []
|
||||
include:
|
||||
- EXC0001
|
||||
- EXC0002
|
||||
- EXC0003
|
||||
- EXC0004
|
||||
- EXC0005
|
||||
- EXC0006
|
||||
- EXC0007
|
||||
- EXC0008
|
||||
- EXC0009
|
||||
- EXC0010
|
||||
- EXC0011
|
||||
- EXC0012
|
||||
- EXC0013
|
||||
- EXC0014
|
||||
- EXC0015
|
||||
# Maximum issues count per one linter.
|
||||
# Set to 0 to disable.
|
||||
# Default: 50
|
||||
max-issues-per-linter: 0
|
||||
# Maximum count of issues with the same text.
|
||||
# Set to 0 to disable.
|
||||
# Default: 3
|
||||
max-same-issues: 0
|
||||
# Show only new issues: if there are unstaged changes or untracked files,
|
||||
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
|
||||
# It's a super-useful option for integration of golangci-lint into existing large codebase.
|
||||
# It's not practical to fix all existing issues at the moment of integration:
|
||||
# much better don't allow issues in new code.
|
||||
#
|
||||
# Default: false.
|
||||
new: false
|
||||
# Show only new issues created after git revision `REV`.
|
||||
# new-from-rev: HEAD
|
||||
# Show only new issues created in git patch with set file path.
|
||||
# new-from-patch: path/to/patch/file
|
||||
# Fix found issues (if it's supported by the linter).
|
||||
fix: true
|
||||
|
||||
severity:
|
||||
# Set the default severity for issues.
|
||||
#
|
||||
# If severity rules are defined and the issues do not match or no severity is provided to the rule
|
||||
# this will be the default severity applied.
|
||||
# Severities should match the supported severity names of the selected out format.
|
||||
# - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity
|
||||
# - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#SeverityLevel
|
||||
# - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
|
||||
# - TeamCity: https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance
|
||||
#
|
||||
# Default value is an empty string.
|
||||
default-severity: error
|
||||
# If set to true `severity-rules` regular expressions become case-sensitive.
|
||||
# Default: false
|
||||
case-sensitive: true
|
||||
# When a list of severity rules are provided, severity information will be added to lint issues.
|
||||
# Severity rules have the same filtering capability as exclude rules
|
||||
# except you are allowed to specify one matcher per severity rule.
|
||||
# Only affects out formats that support setting severity information.
|
||||
#
|
||||
# Default: []
|
||||
rules:
|
||||
- linters:
|
||||
- dupl
|
||||
severity: info
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,53 @@
|
|||
include hack/make-rules/common.mk
|
||||
include hack/make-rules/copyright.mk
|
||||
include hack/make-rules/image.mk
|
||||
include hack/make-rules/golang.mk
|
||||
include hack/make-rules/grpc.mk
|
||||
include hack/make-rules/swagger.mk
|
||||
include hack/make-rules/tools.mk
|
||||
|
||||
ROOT_PACKAGE=github.com/Bio-OS/bioos
|
||||
VERSION ?= $(shell git describe --tags --always --dirty)
|
||||
GIT_BRANCH ?= $(shell git branch | grep \* | cut -d ' ' -f2)
|
||||
GIT_COMMIT ?= $(shell git rev-parse HEAD)
|
||||
GIT_TREE_STATE ?= $(if $(shell git status --porcelain),dirty,clean)
|
||||
GIT_BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
GO_VERSION ?= $(shell go version)
|
||||
|
||||
|
||||
.PHONY: build
|
||||
build: go.build web
|
||||
|
||||
.PHONY: image
|
||||
image: image.build
|
||||
|
||||
.PHONY: push
|
||||
push: image.push
|
||||
|
||||
.PHONY: tools
|
||||
tools:
|
||||
@$(MAKE) tools.install
|
||||
|
||||
.PHONY: swagger
|
||||
swagger: swagger.run
|
||||
|
||||
## web install
|
||||
.PHONY: web.install
|
||||
web.install:
|
||||
npm --prefix=web install
|
||||
|
||||
## web dev
|
||||
.PHONY: web.run
|
||||
web.run:
|
||||
npm --prefix=web run dev
|
||||
|
||||
## web build
|
||||
.PHONY: web
|
||||
web:
|
||||
@echo "===========> Building web"
|
||||
npm --prefix=web run build
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@echo "===========> Cleaning all build binary"
|
||||
@-rm -vrf $(OUTPUT_DIR)
|
|
@ -0,0 +1,2 @@
|
|||
Copyright 2023 Beijing Volcano Engine Technology Ltd.
|
||||
Copyright 2023 Guangzhou Laboratory
|
|
@ -0,0 +1,720 @@
|
|||
# About Bio-OS
|
||||
Bio-OS is an open-source platform for genomics users. It provides a workspace which encapsulates data, workflows, Notebooks, job history, etc. Bio-OS provides both GUI and CLI(Command Line Interface) to quickly setup and run workflows specified by languages like WDL and notebook as well.
|
||||
|
||||
## Bio-OS Concept
|
||||

|
||||
|
||||
----
|
||||
## Bio-OS Workspace
|
||||
Workspace is a complete encapsulation of a bioinformatics research process, including data, environment, code, operational calculation procedures, results, and dashboard as an overview. It is the basic unit that realizes executable, transportable, reproducible, shareable and publishable scientific research and biological application.
|
||||

|
||||
|
||||
----
|
||||
## Architecture :
|
||||
In Bio-OS, there are three parts in the architecture.
|
||||
### Control layer:
|
||||
1. Bioos-web: the front-end component of Bio-OS.
|
||||
2. Bioctl is the command-line component of Bio-OS. Users can access Bio-OS through bioctl.
|
||||
3. OpenAPI is used to describe, produce, consume, and visualize RESTful web services. It's a specification standard for REST APIs that defines the structure and syntax.
|
||||
4. Bioos-apiserver: It mainly realizes the management of workspace, workflow (conforming to WES specification), data, etc. The bioos-server will send requests to the clusters of each computing plane to implement the running workflow
|
||||
5. IAM(not supported in Version 1.0.0): service that helps you securely control user access
|
||||
### Workflow Layer
|
||||
1. Cromwell: open-source cromwell version v85
|
||||
2. Storage : store workflow input ,output data or log
|
||||
### Notebook Layer
|
||||
1. JupyterHub: a multi-user Hub that spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server.
|
||||
2. Storage : Used to store data from jupyter notebook
|
||||
|
||||

|
||||
|
||||
## Capabilities
|
||||
There are four core capabilities which support Bio-OS.
|
||||
- Data Management
|
||||
|
||||
Effectively organize massive scale sample data and easily access public data
|
||||
|
||||
- Application Management
|
||||
|
||||
Bring together data、workflow、Notebooks、work engine into Sharable、 Traceable and reproducible Workspace
|
||||
|
||||
- Resource Management
|
||||
|
||||
Autoscaling through cloud-native which supports hybrid scheduling of multi-cloud heterogeneous resources
|
||||
|
||||
- Interactive Analysis
|
||||
|
||||
Using Built-in Notebook to do tertiary analysis
|
||||
|
||||
## Getting Started
|
||||
### Prerequisites
|
||||
To run Bio-OS, the following prerequisites must be met:
|
||||
- A computer with one of the following operating systems:
|
||||
- CentOS 7.9 +
|
||||
- Ubuntu 22.04 +
|
||||
- Internet access
|
||||
- Resource Required
|
||||
|
||||
Minimum 8 cpu and 16G memory
|
||||
|
||||
### Installation
|
||||
There will be two ways to install Bio-OS .
|
||||
1. Using Docker-compose
|
||||
|
||||
- cromwell deployment
|
||||
|
||||
Bio-OS V1.0.0 Version does not support cromwell containerized deployment,You need to install java-jre 11 version on the server and configure application.conf.
|
||||
```text
|
||||
include required(classpath("application"))
|
||||
webservice {
|
||||
port = 8000
|
||||
}
|
||||
workflow-options {
|
||||
workflow-log-dir = /nfs/bioos-storage/cromwell-workflow-logs
|
||||
workflow-log-temporary = false
|
||||
}
|
||||
call-caching {
|
||||
enabled = true
|
||||
invalidate-bad-cache-results = true
|
||||
}
|
||||
database {
|
||||
profile = "slick.jdbc.MySQLProfile$"
|
||||
db {
|
||||
driver = "com.mysql.cj.jdbc.Driver"
|
||||
url = "jdbc:mysql://180.184.37.106:3306/wqw?rewriteBatchedStatements=true&useSSL=false"
|
||||
port = 3306
|
||||
user = "public"
|
||||
password = "Mirrors79"
|
||||
connectionTimeout = 5000
|
||||
}
|
||||
}
|
||||
backend {
|
||||
default = "Local"
|
||||
providers {
|
||||
Local {
|
||||
config {
|
||||
root = "/nfs/bioos-storage/cromwell-executions"
|
||||
filesystem {
|
||||
local {
|
||||
localization: [
|
||||
"hard-link", "soft-link", "copy"
|
||||
]
|
||||
|
||||
caching {
|
||||
duplication-strategy: [
|
||||
"hard-link", "soft-link", "copy"
|
||||
]
|
||||
hashing-strategy: "md5"
|
||||
check-sibling-md5: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
**Attention**: The workflow-log-dir configuration should be consistent with the apiserver reference storage configuration. Execute the following code to complete the cromwell local deployment, and the apiserver in the subsequent docker-compose.yaml will refer to the cromwell call.
|
||||
```shell
|
||||
java -jar -Dconfig.file=/root/cromwell/application.conf -DLOG_LEVEL=INFO -DLOG_MODE=standard /root/cromwell/cromwell.jar server
|
||||
```
|
||||
- Bioos local startup
|
||||
|
||||
For local container environment testing, you need to install the docker container environment. Our code base provides the'docker-compose.yaml 'file.
|
||||
|
||||
Local environment testing can be quickly completed with the following commands.
|
||||
```shell
|
||||
# 进入开源 bioos 目录,执行命令
|
||||
docker-compose up -d
|
||||
```
|
||||
**Note**: Local deployment will involve the problem of jupyterhub dynamically obtaining tokens. You can refer to the online deployment part. To update the token, you need to restart the bioos-apiserver container.
|
||||
|
||||
2. Online Deployment
|
||||
|
||||
Online deployment requires the preparation of a container orchestration engine, and we provide helm deployment packages to quickly complete bioos deployment.
|
||||
|
||||
- Deployment based on Kubernetes environment
|
||||
|
||||
If there is no local Kubernetes environment, you can try to deploy minikube. For details, please refer to the [official website](https://minikube.sigs.k8s.io/docs/start/).
|
||||
|
||||
When installing the container runtime, you have two options:
|
||||
- Docker - The most common container runtime environment
|
||||
- Deployment method
|
||||
```shell
|
||||
sudo apt update && apt install docker.io -y
|
||||
```
|
||||
- Nerdctl - Open source and open operating environment, a perfect alternative to the cncf community
|
||||
- Deployment method
|
||||
```shell
|
||||
wget https://github.com/containerd/nerdctl/releases/download/v1.4.0/nerdctl-full-1.4.0-linux-amd64.tar.gz
|
||||
tar zxf nerdctl-full-1.4.0-linux-amd64.tar.gz -C /usr/local/
|
||||
cp /usr/local/lib/systemd/system/*.service /etc/systemd/system/
|
||||
ln -s /usr/local/bin/nerdctl /usr/bin/docker
|
||||
systemctl enable buildkit containerd
|
||||
systemctl restart buildkit containerd
|
||||
```
|
||||
Kubernetes environment also has many deployment methods, common deployment forms such as minikube/microk8s/kubeadm/kubespray, considering the simplicity and ease of use, here take minikube as an example to build the local environment, production environment recommends choosing kubespray for high availability deployment.
|
||||
```shell
|
||||
# 安装基础依赖
|
||||
sudo apt update && apt install -y conntrack
|
||||
sudo sysctl fs.protected_regular=0 #重启之后,记得执行一下,避免 minikube /tmp/juju-mkf6a06118463380f4d96c12aced04598f450743: permission denied 类似报错
|
||||
mkdir -p /etc/containerd && containerd config default > /etc/containerd/config.toml
|
||||
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.27.0/crictl-v1.27.0-linux-amd64.tar.gz
|
||||
tar xvf crictl-v1.27.0-linux-amd64.tar.gz -C /usr/bin/
|
||||
# 安装 minikube
|
||||
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
|
||||
sudo install minikube-linux-amd64 /usr/local/bin/minikube
|
||||
# 使用国内源安装 kubernetes
|
||||
minikube start \
|
||||
--container-runtime="containerd" \
|
||||
--image-mirror-country=cn \
|
||||
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers \
|
||||
--driver=none \
|
||||
--apiserver-ips='180.184.43.61' # 可选配置,如果需要外网访问,需要这里配置主机的外网IP
|
||||
```
|
||||
Deployment log information:
|
||||
```shell
|
||||
root@registry:/home/vagrant# minikube start --container-runtime="containerd" --image-mirror-country=cn --driver=none --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containeres
|
||||
😄 minikube v1.30.1 on Ubuntu 22.04 (amd64)
|
||||
✨ Using the none driver based on existing profile
|
||||
❗ Using the 'containerd' runtime with the 'none' driver is an untested configuration!
|
||||
❗ Using the 'containerd' runtime with the 'none' driver is an untested configuration!
|
||||
👍 Starting control plane node minikube in cluster minikube
|
||||
🔄 Restarting existing none bare metal machine for "minikube" ...
|
||||
ℹ️ OS release is Ubuntu 22.04.1 LTS
|
||||
📦 Preparing Kubernetes v1.26.3 on containerd 1.6.12-0ubuntu1 ...
|
||||
▪ kubelet.resolv-conf=/run/systemd/resolve/resolv.conf
|
||||
> kubectl.sha256: 64 B / 64 B [-------------------------] 100.00% ? p/s 0s
|
||||
> kubeadm.sha256: 64 B / 64 B [-------------------------] 100.00% ? p/s 0s
|
||||
> kubelet.sha256: 64 B / 64 B [-------------------------] 100.00% ? p/s 0s
|
||||
> kubectl: 45.81 MiB / 45.81 MiB [-------------] 100.00% 5.61 MiB p/s 8.4s
|
||||
> kubeadm: 44.61 MiB / 44.61 MiB [--------------] 100.00% 2.27 MiB p/s 20s
|
||||
> kubelet: 115.65 MiB / 115.65 MiB [------------] 100.00% 4.39 MiB p/s 27s
|
||||
▪ Generating certificates and keys ...
|
||||
▪ Booting up control plane ...
|
||||
▪ Configuring RBAC rules ...
|
||||
🔗 Configuring bridge CNI (Container Networking Interface) ...
|
||||
🤹 Configuring local host environment ...
|
||||
|
||||
❗ The 'none' driver is designed for experts who need to integrate with an existing VM
|
||||
💡 Most users should use the newer 'docker' driver instead, which does not require root!
|
||||
📘 For more information, see: https://minikube.sigs.k8s.io/docs/reference/drivers/none/
|
||||
|
||||
❗ kubectl and minikube configuration will be stored in /root
|
||||
❗ To use kubectl or minikube commands as your own user, you may need to relocate them. For example, to overwrite your own settings, run:
|
||||
|
||||
▪ sudo mv /root/.kube /root/.minikube $HOME
|
||||
▪ sudo chown -R $USER $HOME/.kube $HOME/.minikube
|
||||
|
||||
💡 This can also be done automatically by setting the env var CHANGE_MINIKUBE_NONE_USER=true
|
||||
▪ Using image registry.cn-hangzhou.aliyuncs.com/google_containers/storage-provisioner:v5
|
||||
🔎 Verifying Kubernetes components...
|
||||
🌟 Enabled addons: default-storageclass, storage-provisioner
|
||||
💡 kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
|
||||
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
|
||||
```
|
||||
After installing minikube, we still need to do some small configuration, such as configuring network plugins
|
||||
```shell
|
||||
# 追加 kubectl 快捷方式
|
||||
echo 'alias kubectl="minikube kubectl --"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
# 配置 calico cni 网络
|
||||
mkdir -p /opt/cni/bin && wget https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz
|
||||
tar -xvf cni-plugins-linux-amd64-v1.3.0.tgz -C /opt/cni/bin
|
||||
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.1/manifests/calico.yaml
|
||||
```
|
||||
Environmental inspection, the final deployment situation is as follows:
|
||||
```shell
|
||||
root@registry:/home/vagrant# kubectl get po -A
|
||||
NAMESPACE NAME READY STATUS RESTARTS AGE
|
||||
kube-system calico-kube-controllers-5857bf8d58-2p25d 1/1 Running 0 7m46s
|
||||
kube-system calico-node-fk6vd 1/1 Running 0 7m46s
|
||||
kube-system coredns-567c556887-8r8cx 1/1 Running 0 11m
|
||||
kube-system etcd-registry 1/1 Running 2 11m
|
||||
kube-system kube-apiserver-registry 1/1 Running 2 12m
|
||||
kube-system kube-controller-manager-registry 1/1 Running 2 11m
|
||||
kube-system kube-proxy-vt2ks 1/1 Running 0 11m
|
||||
kube-system kube-scheduler-registry 1/1 Running 2 11m
|
||||
kube-system storage-provisioner 1/1 Running 0 11m
|
||||
```
|
||||
Since Bioos needs to use NAS storage, we choose to use the NFS storage solution here. For convenient access, we also need to install an Ingress controller to provide network access support.
|
||||
|
||||
**Install the ingress controller**
|
||||
|
||||
Ingress deployment can refer to the official website, and you can also refer to the nginx-ingress deployment configuration of kubespray.
|
||||
```shell
|
||||
# 安装 ingress 控制器(国内可能镜像下载会有问题)
|
||||
#kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.7.1/deploy/static/provider/cloud/deploy.yaml
|
||||
# 如果镜像下载有问题,可以使用下面的代码替代
|
||||
# kubectl apply -f https://raw.githubusercontent.com/markthink/helm-charts/main/StorageClass/base/yaml/ingress-nginx.yaml
|
||||
git clone https://github.com/markthink/helm-charts
|
||||
cd helm-charts && kubectl apply -f ingress_nginx/
|
||||
```
|
||||
Environmental inspection, the final deployment situation is as follows:
|
||||
```shell
|
||||
root@registry:/home/vagrant# kubectl get po -A
|
||||
NAMESPACE NAME READY STATUS RESTARTS AGE
|
||||
ingress-nginx ingress-nginx-controller-f6c55fdc8-x5dtz 0/1 Running 0 40s
|
||||
kube-system calico-kube-controllers-5857bf8d58-2p25d 1/1 Running 0 15m
|
||||
kube-system calico-node-fk6vd 1/1 Running 0 15m
|
||||
kube-system coredns-567c556887-8r8cx 1/1 Running 0 19m
|
||||
kube-system etcd-registry 1/1 Running 2 19m
|
||||
kube-system kube-apiserver-registry 1/1 Running 2 19m
|
||||
kube-system kube-controller-manager-registry 1/1 Running 2 19m
|
||||
kube-system kube-proxy-vt2ks 1/1 Running 0 19m
|
||||
kube-system kube-scheduler-registry 1/1 Running 2 19m
|
||||
kube-system storage-provisioner 1/1 Running 0 19m
|
||||
root@registry:/home/vagrant# kubectl get ingressclass -A
|
||||
NAME CONTROLLER PARAMETERS AGE
|
||||
nginx k8s.io/ingress-nginx <none> 47s
|
||||
```
|
||||
**Install NFS Storage Service**
|
||||
|
||||
Use helm to install the nfs storage service (please refer to the remarks for Nfs-server deployment)
|
||||
Since you can't download foreign (registry.k8s.io) images in China, you need to prepare the helm configuration file values.yaml:
|
||||
```yaml
|
||||
image:
|
||||
nfs:
|
||||
repository: dyrnq/nfsplugin
|
||||
tag: v4.2.0
|
||||
pullPolicy: IfNotPresent
|
||||
csiProvisioner:
|
||||
repository: dyrnq/csi-provisioner
|
||||
tag: v3.3.0
|
||||
pullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
repository: dyrnq/livenessprobe
|
||||
tag: v2.8.0
|
||||
pullPolicy: IfNotPresent
|
||||
nodeDriverRegistrar:
|
||||
repository: dyrnq/csi-node-driver-registrar
|
||||
tag: v2.6.2
|
||||
pullPolicy: IfNotPresent
|
||||
```
|
||||
```shell
|
||||
# 安装 helm 部署包
|
||||
wget https://get.helm.sh/helm-v3.12.0-linux-amd64.tar.gz
|
||||
tar xvf helm-v3.12.0-linux-amd64.tar.gz \
|
||||
--strip-components=1 -C /usr/local/bin
|
||||
# 默认配置一般不需要修改
|
||||
# https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/charts/v4.2.0/csi-driver-nfs/values.yaml
|
||||
# 安装 NFS CSI 存储驱动
|
||||
helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
|
||||
helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs --namespace kube-system --version v4.2.0 -f values.yaml
|
||||
```
|
||||
Use Kustomize to process related variables, [Code link](https://github.com/markthink/helm-charts/tree/main/StorageClass):
|
||||
```shell
|
||||
root@opensource-bioos:~/helm-charts/storageclass# tree .
|
||||
.
|
||||
├── base
|
||||
│ ├── kustomization.yaml
|
||||
│ └── yaml
|
||||
│ ├── ingress-nginx.yaml
|
||||
│ └── sc.yaml
|
||||
└── overlays
|
||||
├── dev
|
||||
│ └── kustomization.yaml
|
||||
└── prod
|
||||
└── kustomization.yaml
|
||||
|
||||
5 directories, 5 files
|
||||
```
|
||||
The configuration file directory structure is as above, base is the basic resource configuration file, overlays the related variables according to different environment configuration, enter the directory and execute the following command to view the generation result. Before generating, you need to modify the kustomization.yaml file
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
# https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patches/
|
||||
# https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patchesjson6902/
|
||||
resources:
|
||||
# - yaml/ingress-nginx.yaml
|
||||
- yaml/sc.yaml
|
||||
|
||||
patches:
|
||||
- patch: |-
|
||||
- op: replace
|
||||
path: /parameters/server
|
||||
value: 192.168.46.300 #这里填写 nfs server 地址
|
||||
- op: replace
|
||||
path: /parameters/share
|
||||
value: /nfs #这里填写 nfs server 根目录地址
|
||||
target:
|
||||
kind: StorageClass
|
||||
name: nfs-csi
|
||||
|
||||
- patch: |-
|
||||
- op: replace
|
||||
path: /spec/csi/volumeAttributes/server
|
||||
value: 192.168.46.300
|
||||
- op: replace
|
||||
path: /spec/csi/volumeAttributes/share
|
||||
value: /nfs
|
||||
- op: replace
|
||||
path: /spec/csi/volumeHandle
|
||||
value: 192.168.46.300#nfs#bioos-storage#
|
||||
- op: replace
|
||||
path: /spec/capacity/storage
|
||||
value: 50Gi
|
||||
target:
|
||||
kind: PersistentVolume
|
||||
name: bioos-storage
|
||||
|
||||
- patch: |-
|
||||
- op: replace
|
||||
path: /spec/resources/requests/storage
|
||||
value: 50Gi #这里配置存储块的大小
|
||||
target:
|
||||
kind: PersistentVolumeClaim
|
||||
name: bioos-storage-pvc
|
||||
```
|
||||
Fill in the correct server related information and execute the command to check that it is correct
|
||||
```shell
|
||||
# kubectl kustomize <kustomization_directory>
|
||||
cd overlays/dev && kubectl kustomize .
|
||||
```
|
||||
Execute the following command to complete the resource creation.
|
||||
```shell
|
||||
# kubectl apply -k <kustomization_directory>
|
||||
cd overlays/dev && kubectl apply -k .
|
||||
```
|
||||
|
||||
**Install Bioos Service**
|
||||
- Install mysql service
|
||||
|
||||
Execute the following command to complete the mysql deployment
|
||||
```shell
|
||||
# 注意要保证 nfs-server 共享目录有 777 权限
|
||||
# https://github.com/bitnami/charts/blob/main/bitnami/mysql/values.yaml
|
||||
helm install mysql \
|
||||
--set auth.rootPassword="admin",auth.database=bioos,auth.username=admin,auth.password=admin,global.storageClass=nfs-csi,primary.persistence.size=50Gi \
|
||||
oci://registry-1.docker.io/bitnamicharts/mysql
|
||||
```
|
||||
- Prepare Jupyterhub/cromwell
|
||||
|
||||
Bioos uses helm packaging, which is currently divided into four sub-packages. It should be noted that after Jupyterhub is deployed, open the browser to obtain new tokens to continue deploying bioos-server and web services.
|
||||
|
||||
The corresponding Helm installation command is as follows:
|
||||
```shell
|
||||
# 添加 helm 仓库
|
||||
helm repo add bioos https://markthink.github.io/helm-charts
|
||||
helm search repo bioos
|
||||
```
|
||||
Search the helm repository using the command
|
||||
```shell
|
||||
helm search repo bioos
|
||||
NAME CHART VERSION APP VERSION DESCRIPTION
|
||||
bioos/bioos 0.1.0 v1 BioOS UI 前端
|
||||
bioos/cromwell 0.1.0 1.0.0 A Helm chart for cromwell with local backend
|
||||
bioos/jupyterhub 2.0.0 3.0.0 Multi-user Jupyter installation
|
||||
```
|
||||
Install cromwell to update the values.yaml configuration file:
|
||||
```yaml
|
||||
## platformConfig contains information about the environment on which the Chart is being installed
|
||||
## These values are expected be updated during the platform installation stage
|
||||
platformConfig:
|
||||
## Container registry for all images involved in the chart
|
||||
registryDomain: docker.io
|
||||
## Container repository for platform components
|
||||
registryRepository: broadinstitute
|
||||
## Platform-wide image pull policy
|
||||
imagePullPolicy: Always
|
||||
## ImagePullSecret name for all images involved in the chart
|
||||
imagePullSecret: ""
|
||||
|
||||
labels: {}
|
||||
|
||||
podLabels: {}
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
|
||||
nodeSelector: {}
|
||||
|
||||
## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
|
||||
tolerations: []
|
||||
|
||||
image:
|
||||
name: cromwell
|
||||
tag: 85
|
||||
replicaCount: 1
|
||||
priorityClassName: ""
|
||||
securityContext:
|
||||
enabled: false
|
||||
runAsUser: 65534 # nobody
|
||||
runAsGroup: 65534 # nobody
|
||||
fsGroup: 65534 # nobody
|
||||
hardAntiAffinity: false
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
successThreshold: 1
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
successThreshold: 1
|
||||
startupProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 10
|
||||
successThreshold: 1
|
||||
resources:
|
||||
limits: {}
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 512Mi
|
||||
config:
|
||||
dir: /etc/cromwell
|
||||
file: config
|
||||
service:
|
||||
type: ClusterIP
|
||||
httpPort: 8000
|
||||
# clusterIP: None
|
||||
# loadBalancerIP:
|
||||
# loadBalancerSourceRanges:
|
||||
# - 10.10.10.0/24
|
||||
annotations: {}
|
||||
labels: {}
|
||||
persistence:
|
||||
enabled: true
|
||||
internal: false
|
||||
pvcName: bioos-storage-pvc
|
||||
storageClass: "nfs-csi"
|
||||
pvcSize: 50Gi
|
||||
accessMode: ReadWriteMany
|
||||
log:
|
||||
path: /cromwell-workflow-logs
|
||||
|
||||
basePath: /nfs/bioos-storage
|
||||
|
||||
executionPath: /cromwell-executions
|
||||
|
||||
db:
|
||||
local:
|
||||
enabled: false
|
||||
path: /cromwell-db
|
||||
mysql:
|
||||
enabled: true
|
||||
host: "mysql"
|
||||
port: 3306
|
||||
name: "bioos"
|
||||
username: "root"
|
||||
password: "admin"
|
||||
|
||||
```
|
||||
install cromwell
|
||||
```shell
|
||||
helm install cromwell bioos/cromwell -f values.yaml
|
||||
```
|
||||
Setup jupyterhub configure file values.yaml:
|
||||
```yaml
|
||||
imagePullSecrets: []
|
||||
hub:
|
||||
baseUrl: "/jupyterhub/"
|
||||
allowNamedServers: true
|
||||
config:
|
||||
NologinAuthenticator:
|
||||
username: nobody
|
||||
JupyterHub:
|
||||
admin_access: true
|
||||
authenticator_class: myauthenticator.NologinAuthenticator
|
||||
tornado_settings:
|
||||
slow_spawn_timeout: 0
|
||||
headers:
|
||||
Access-Control-Allow-Origin: "*"
|
||||
Content-Security-Policy: "frame-ancestors 'self' http://localhost"
|
||||
Spawner:
|
||||
args:
|
||||
- "--NotebookApp.allow_origin=*"
|
||||
- '--NotebookApp.tornado_settings={"headers":{"Content-Security-Policy": "frame-ancestors ''self'' http://localhost"}}'
|
||||
image:
|
||||
name: bioos/jupyterhub
|
||||
tag: "v1.0"
|
||||
networkPolicy:
|
||||
enabled: false
|
||||
db:
|
||||
type: mysql
|
||||
url: mysql+pymysql://root:admin@mysql:3306/bioos
|
||||
|
||||
singleuser:
|
||||
networkTools:
|
||||
image:
|
||||
name: jupyterhub/k8s-network-tools
|
||||
image:
|
||||
name: jupyterhub/k8s-singleuser-sample
|
||||
storage:
|
||||
type: none
|
||||
cpu:
|
||||
limit: 1
|
||||
guarantee: 1
|
||||
memory:
|
||||
limit: 1G
|
||||
guarantee: 1G
|
||||
|
||||
scheduling:
|
||||
userScheduler:
|
||||
enabled: false
|
||||
userPlaceholder:
|
||||
enabled: false
|
||||
|
||||
prePuller:
|
||||
hook:
|
||||
enabled: false
|
||||
continuous:
|
||||
enabled: false
|
||||
|
||||
```
|
||||
Execute the following commands to install jupyterhub:
|
||||
```shell
|
||||
helm install jupyterhub bioos/jupyterhub -f values.yaml
|
||||
```
|
||||
Open a browser to visit: http://serverIP/jupyterhub, and apply for a new token.
|
||||
|
||||
**Install Bioos Service**
|
||||
|
||||
You need to update the configuration of bioos. The specific path is as follows:
|
||||
- You need to update the configuration of bioos. The specific path is as follows: bioos/values.yaml
|
||||
|
||||
Modify the configuration and execute the command as follows
|
||||
```yaml
|
||||
image:
|
||||
repository: docker.io
|
||||
project: bioos
|
||||
web_name: web:v1.2
|
||||
apiserver_name: apiserver:v1.2
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
replicaCount: 1
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
annotations: {}
|
||||
name: ""
|
||||
|
||||
rbac:
|
||||
create: true
|
||||
|
||||
podAnnotations: {}
|
||||
podSecurityContext: {}
|
||||
|
||||
securityContext: {}
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
web_port: 80
|
||||
api_http_port: 8888
|
||||
api_grpc_port: 50051
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: ""
|
||||
annotations:
|
||||
{
|
||||
# https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#custom-max-body-size
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "64m"
|
||||
}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: ""
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
|
||||
resources: {}
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
notebook:
|
||||
images:
|
||||
- name: datascience
|
||||
version: "1.0"
|
||||
description: "this is official image"
|
||||
image: jupyter/datascience-notebook:hub-3.0.0
|
||||
resources:
|
||||
- cpu: 1
|
||||
memory: 1Gi
|
||||
disk: 20Gi
|
||||
- cpu: 2
|
||||
memory: 4Gi
|
||||
disk: 20Gi
|
||||
|
||||
mysql:
|
||||
hostname: mysql
|
||||
database: bioos
|
||||
username: root
|
||||
password: admin
|
||||
|
||||
storage:
|
||||
pvc: bioos-storage-pvc
|
||||
mountPath: /app/conf/fs
|
||||
|
||||
wes:
|
||||
endpoint: http://180.184.43.61:8000
|
||||
|
||||
jupyterhub:
|
||||
endpoint: http://180.184.43.61/jupyterhub
|
||||
adminToken: 6026738d798c495aa01c7831048539d9
|
||||
```
|
||||
Update config and run the following command
|
||||
```shell
|
||||
helm install demo bioos/bioos -f values.yaml
|
||||
```
|
||||
**Environmental inspection**
|
||||
|
||||
Execute the command'kubectl get po, pv, pvc, ing 'to view the application deployment status. Bioos uses two pieces of storage, one for Mysql and one for bioos storage, and provides several ingress rules to facilitate subpath reference services. The command status should look like this:
|
||||
```shell
|
||||
# kubectl get po,pv,pvc,ing
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pod/apiserver-bd4f89b4c-46vgp 1/1 Running 0 147m
|
||||
pod/hub-78c55bf4c8-tr8v8 1/1 Running 0 149m
|
||||
pod/jupyter-nobody 1/1 Running 0 17m
|
||||
pod/mysql-77b4f7f77c-b9smq 1/1 Running 0 150m
|
||||
pod/web-7ff595699-rnmkf 1/1 Running 0 147m
|
||||
|
||||
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
|
||||
persistentvolume/bioos-storage 10Gi RWO Delete Bound default/bioos-storage-pvc nfs-csi 150m
|
||||
persistentvolume/pvc-6b827f42-82e5-40b2-8e74-2c56fb8d836e 10Gi RWO Delete Bound default/mysql-pv-claim nfs-csi 150m
|
||||
|
||||
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
|
||||
persistentvolumeclaim/bioos-storage-pvc Bound bioos-storage 10Gi RWO nfs-csi 150m
|
||||
persistentvolumeclaim/mysql-pv-claim Bound pvc-6b827f42-82e5-40b2-8e74-2c56fb8d836e 10Gi RWO nfs-csi 150m
|
||||
|
||||
NAME CLASS HOSTS ADDRESS PORTS AGE
|
||||
ingress.networking.k8s.io/demo-bioos nginx * 10.211.55.13 80 147m
|
||||
ingress.networking.k8s.io/jupyter--2fjupyterhub-2f-route nginx * 10.211.55.13 80 149m
|
||||
ingress.networking.k8s.io/jupyter--2fjupyterhub-2fuser-2fnobody-2f-route nginx * 10.211.55.13 80 17m
|
||||
```
|
||||
Open a browser and visit http://serverIP/workspace
|
||||
|
||||
This completes the bioos installation.
|
||||
|
||||
## Tutorial
|
||||
### Bio-OS user guide
|
||||
Please look at the Bio-OS user guide.
|
||||
### Bio-OS CLI
|
||||
Please look at the CLI tutorial.
|
||||
|
||||
## License
|
||||
This project is licensed under the Apache-2.0 License.
|
|
@ -0,0 +1,23 @@
|
|||
FROM golang:1.19.8-bullseye AS builder
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
WORKDIR /go/src/github.com/Bio-OS/bioos
|
||||
COPY go.mod .
|
||||
RUN --mount=type=cache,target=/go/pkg/mod go mod download
|
||||
COPY . .
|
||||
RUN go env -w CGO_ENABLED=1 && BINS=apiserver PLATFORM=${TARGETOS}_${TARGETARCH} make go.build && make tools.install.cfssl && make tools.install.womtool && make generate.certs
|
||||
|
||||
FROM debian:bullseye
|
||||
RUN apt update \
|
||||
&& apt install -y --no-install-recommends ca-certificates openjdk-11-jre-headless \
|
||||
&& apt clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
USER nobody
|
||||
WORKDIR /app
|
||||
COPY --from=builder --chown=nobody:nogroup /go/src/github.com/Bio-OS/bioos/conf conf
|
||||
COPY --from=builder /go/src/github.com/Bio-OS/bioos/_output/platforms/${TARGETOS}/${TARGETARCH}/apiserver .
|
||||
COPY --from=builder /go/src/github.com/Bio-OS/bioos/womtool.jar .
|
||||
ENTRYPOINT ["/app/apiserver"]
|
|
@ -0,0 +1,18 @@
|
|||
FROM golang:1.19.8-bullseye AS builder
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
WORKDIR /go/src/github.com/Bio-OS/bioos
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
RUN --mount=type=cache,target=/go/pkg/mod go mod download
|
||||
COPY . .
|
||||
RUN BINS=bioctl PLATFORM=${TARGETOS}_${TARGETARCH} make go.build
|
||||
|
||||
FROM debian:bullseye
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
USER nobody
|
||||
WORKDIR /app
|
||||
COPY --from=builder /go/src/github.com/Bio-OS/bioos/_output/platforms/${TARGETOS}/${TARGETARCH}/bioctl .
|
||||
ENTRYPOINT ["sleep", "1d"]
|
|
@ -0,0 +1,10 @@
|
|||
FROM node:16-alpine3.16 as build
|
||||
WORKDIR /usr/app
|
||||
COPY /web /usr/app/
|
||||
RUN npm install && npm run build
|
||||
|
||||
FROM nginx:1.21-alpine
|
||||
COPY --from=build /usr/app/build /usr/app/
|
||||
COPY --from=build /usr/app/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
EXPOSE 80
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// Copyright 2023 Beijing Volcano Engine Technology Ltd.
|
||||
// Copyright 2023 Guangzhou Laboratory
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
bioos_server "github.com/Bio-OS/bioos/internal/apiserver"
|
||||
)
|
||||
|
||||
// @title BioOS Apiserver
|
||||
// @version 1.0
|
||||
// @description This is bioos apiserver using Hertz.
|
||||
|
||||
// @contact.name hertz-contrib
|
||||
// @contact.url https://github.com/hertz-contrib
|
||||
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
// @BasePath /
|
||||
// @schemes http https
|
||||
// @securityDefinitions.basic basicAuth
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
command := bioos_server.NewBioosServerCommand(ctx)
|
||||
if err := command.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// Copyright 2023 Beijing Volcano Engine Technology Ltd.
|
||||
// Copyright 2023 Guangzhou Laboratory
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"k8s.io/component-base/cli"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
command := bioctl.NewDefaultBIOCtlCommand()
|
||||
if err := cli.RunNoErrOutput(command); err != nil {
|
||||
utils.CheckErr(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
server:
|
||||
http:
|
||||
port: 8888
|
||||
tls: false
|
||||
grpc:
|
||||
port: 50051
|
||||
tls: true
|
||||
cert-file: conf/certs/server.pem
|
||||
key-file: conf/certs/server-key.pem
|
||||
ca-file: conf/certs/ca.pem
|
||||
womtool-file: womtool.jar
|
||||
|
||||
log:
|
||||
level: debug
|
||||
output-path: app.log
|
||||
max-backups: 3
|
||||
|
||||
db:
|
||||
mongo:
|
||||
host: MONGO_HOST
|
||||
username: MONGO_USERNAME
|
||||
password: MONGO_PASSWORD
|
||||
port: MONGO_PORT
|
||||
database: MONGO_DB
|
||||
connectTimeout: 30s
|
||||
serverSelectionTimeout: 30s
|
||||
socketTimeout: 30s
|
||||
heartbeatInterval: 5s
|
||||
maxConnIdleTime: 30s
|
||||
maxPoolSize: 100
|
||||
minPoolSize: 1
|
||||
|
||||
|
||||
eventBus:
|
||||
maxRetries: 10
|
||||
syncPeriod: 15s
|
||||
batchSize: 5
|
||||
workers: 5
|
||||
dequeueTimeout: 5m
|
||||
runningTimeout: 24h
|
||||
|
||||
storage:
|
||||
fs:
|
||||
rootPath: /app/conf/fs
|
||||
|
||||
auth:
|
||||
authn:
|
||||
basic:
|
||||
ttl: 5
|
||||
users:
|
||||
- ID: 1
|
||||
name: admin
|
||||
password: admin
|
||||
groups:
|
||||
- admin
|
||||
- users
|
||||
extensions:
|
||||
dept:
|
||||
- deptA
|
||||
- deptB
|
||||
role:
|
||||
- roleA
|
||||
- roleB
|
||||
authz:
|
||||
casbin:
|
||||
model: conf/model.conf
|
||||
policy: conf/policy.csv
|
||||
|
||||
wes:
|
||||
endpoint: ''
|
||||
basePath: /api/ga4gh/wes/v1
|
||||
timeout: 10
|
||||
Retry: 1
|
||||
|
||||
client:
|
||||
serverAddr: localhost:50051
|
||||
serverName: localhost
|
||||
insecure: false
|
||||
serverCertFile: conf/certs/server.pem
|
||||
clientCertFile: conf/certs/client.pem
|
||||
clientCertKeyFile: conf/certs/client-key.pem
|
||||
caFile: conf/certs/ca.pem
|
||||
username: admin
|
||||
password: admin
|
||||
method: grpc
|
||||
timeout: 10
|
||||
|
||||
notebook:
|
||||
officialImages:
|
||||
- name: sample
|
||||
version: '1.0'
|
||||
description: 'this is official images'
|
||||
image: jupyterhub/k8s-singleuser-sample:2.0.0
|
||||
updateTime: '2023-01-01T00:00:00Z'
|
||||
resourceOptions:
|
||||
- cpu: 1
|
||||
memory: 1Gi
|
||||
disk: 20Gi
|
||||
staticJupyterhub:
|
||||
endpoint: '' # url format
|
||||
adminToken: ''
|
||||
kubernetes:
|
||||
namespace: ''
|
|
@ -0,0 +1,107 @@
|
|||
server:
|
||||
http:
|
||||
port: 8888
|
||||
tls: false
|
||||
grpc:
|
||||
port: 50051
|
||||
tls: true
|
||||
cert-file: conf/certs/server.pem
|
||||
key-file: conf/certs/server-key.pem
|
||||
ca-file: conf/certs/ca.pem
|
||||
womtool-file: womtool.jar
|
||||
|
||||
log:
|
||||
level: debug
|
||||
output-path: app.log
|
||||
max-backups: 3
|
||||
|
||||
db:
|
||||
mysql:
|
||||
host: MYSQL_HOST
|
||||
username: MYSQL_USERNAME
|
||||
password: MYSQL_PASSWORD
|
||||
port: MYSQL_PORT
|
||||
database: MYSQL_DB
|
||||
maxOpenConns: 100
|
||||
maxIdleConns: 1
|
||||
connMaxLifetime: 1h
|
||||
connMaxIdletime: 30s
|
||||
|
||||
eventBus:
|
||||
maxRetries: 10
|
||||
syncPeriod: 15s
|
||||
batchSize: 5
|
||||
workers: 5
|
||||
dequeueTimeout: 5m
|
||||
runningTimeout: 24h
|
||||
|
||||
storage:
|
||||
fs:
|
||||
rootPath: /app/conf/fs
|
||||
|
||||
auth:
|
||||
authn:
|
||||
basic:
|
||||
ttl: 5
|
||||
users:
|
||||
- ID: 1
|
||||
name: admin
|
||||
password: admin
|
||||
groups:
|
||||
- admin
|
||||
- users
|
||||
extensions:
|
||||
dept:
|
||||
- deptA
|
||||
- deptB
|
||||
role:
|
||||
- roleA
|
||||
- roleB
|
||||
authz:
|
||||
casbin:
|
||||
model: conf/model.conf
|
||||
policy: conf/policy.csv
|
||||
|
||||
wes:
|
||||
endpoint: 'http://cromwell:8000'
|
||||
basePath: /api/ga4gh/wes/v1
|
||||
timeout: 10
|
||||
Retry: 1
|
||||
|
||||
client:
|
||||
serverAddr: localhost:50051
|
||||
serverName: localhost
|
||||
insecure: false
|
||||
serverCertFile: conf/certs/server.pem
|
||||
clientCertFile: conf/certs/client.pem
|
||||
clientCertKeyFile: conf/certs/client-key.pem
|
||||
caFile: conf/certs/ca.pem
|
||||
username: admin
|
||||
password: admin
|
||||
method: grpc
|
||||
timeout: 10
|
||||
|
||||
notebook:
|
||||
officialImages:
|
||||
- name: sample
|
||||
version: '1.0'
|
||||
description: 'this is official images'
|
||||
image: jupyterhub/k8s-singleuser-sample:2.0.0
|
||||
updateTime: '2023-01-01T00:00:00Z'
|
||||
resourceOptions:
|
||||
- cpu: 1
|
||||
memory: 1Gi
|
||||
disk: 20Gi
|
||||
- cpu: 2
|
||||
memory: 4Gi
|
||||
disk: 20Gi
|
||||
gpu:
|
||||
model: 'Nvidia Tesla T4'
|
||||
memory: 64Gi
|
||||
card: 1
|
||||
nodeSelector: {}
|
||||
staticJupyterhub:
|
||||
endpoint: '' # url format
|
||||
adminToken: ''
|
||||
kubernetes:
|
||||
namespace: ''
|
|
@ -0,0 +1,90 @@
|
|||
server:
|
||||
http:
|
||||
port: 8888
|
||||
tls: false
|
||||
grpc:
|
||||
port: 50051
|
||||
tls: true
|
||||
cert-file: conf/certs/server.pem
|
||||
key-file: conf/certs/server-key.pem
|
||||
ca-file: conf/certs/ca.pem
|
||||
womtool-file: womtool.jar
|
||||
|
||||
log:
|
||||
level: debug
|
||||
output-path: app.log
|
||||
max-backups: 3
|
||||
|
||||
db:
|
||||
sqlite3:
|
||||
file: ":memory:"
|
||||
|
||||
eventBus:
|
||||
maxRetries: 10
|
||||
syncPeriod: 15s
|
||||
batchSize: 5
|
||||
workers: 5
|
||||
dequeueTimeout: 5m
|
||||
runningTimeout: 24h
|
||||
|
||||
storage:
|
||||
fs:
|
||||
rootPath: /tmp
|
||||
auth:
|
||||
authn:
|
||||
basic:
|
||||
ttl: 5
|
||||
users:
|
||||
- ID: 1
|
||||
name: admin
|
||||
password: admin
|
||||
groups:
|
||||
- admin
|
||||
- users
|
||||
extensions:
|
||||
dept:
|
||||
- deptA
|
||||
- deptB
|
||||
role:
|
||||
- roleA
|
||||
- roleB
|
||||
authz:
|
||||
casbin:
|
||||
model: conf/model.conf
|
||||
policy: conf/policy.csv
|
||||
|
||||
wes:
|
||||
endpoint: ''
|
||||
basePath: /api/ga4gh/wes/v1
|
||||
timeout: 10
|
||||
Retry: 1
|
||||
|
||||
client:
|
||||
serverAddr: localhost:50051
|
||||
serverName: localhost
|
||||
insecure: false
|
||||
serverCertFile: conf/certs/server.pem
|
||||
clientCertFile: conf/certs/client.pem
|
||||
clientCertKeyFile: conf/certs/client-key.pem
|
||||
caFile: conf/certs/ca.pem
|
||||
username: admin
|
||||
password: admin
|
||||
method: grpc
|
||||
timeout: 10
|
||||
|
||||
notebook:
|
||||
officialImages:
|
||||
- name: sample
|
||||
version: '1.0'
|
||||
description: 'this is official images'
|
||||
image: jupyterhub/k8s-singleuser-sample:2.0.0
|
||||
updateTime: '2023-01-01T00:00:00Z'
|
||||
resourceOptions:
|
||||
- cpu: 1
|
||||
memory: 1Gi
|
||||
disk: 20Gi
|
||||
staticJupyterhub:
|
||||
endpoint: '' # url format
|
||||
adminToken: ''
|
||||
kubernetes:
|
||||
namespace: ''
|
|
@ -0,0 +1,15 @@
|
|||
client:
|
||||
serverAddr: localhost:50051
|
||||
serverName: localhost
|
||||
insecure: false
|
||||
serverCertFile: conf/certs/server.pem
|
||||
clientCertFile: conf/certs/client.pem
|
||||
clientCertKeyFile: conf/certs/client-key.pem
|
||||
caFile: conf/certs/ca.pem
|
||||
username: admin
|
||||
password: admin
|
||||
method: grpc
|
||||
timeout: 10
|
||||
|
||||
stream:
|
||||
format: yaml
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"signing": {
|
||||
"default": {
|
||||
"expiry": "876000h"
|
||||
},
|
||||
"profiles": {
|
||||
"client": {
|
||||
"expiry": "175200h",
|
||||
"usages": [
|
||||
"signing",
|
||||
"key encipherment",
|
||||
"client auth"
|
||||
]
|
||||
},
|
||||
"peer": {
|
||||
"expiry": "175200h",
|
||||
"usages": [
|
||||
"signing",
|
||||
"key encipherment",
|
||||
"client auth",
|
||||
"server auth"
|
||||
]
|
||||
},
|
||||
"server": {
|
||||
"expiry": "175200h",
|
||||
"usages": [
|
||||
"signing",
|
||||
"key encipherment",
|
||||
"server auth"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"CN": "ca",
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "CN",
|
||||
"ST": "BeiJing",
|
||||
"L": "BeiJing",
|
||||
"O": "Bio",
|
||||
"OU": "MyTeam"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if ! [[ -f ca.pem ]];then
|
||||
./ca.sh
|
||||
fi
|
||||
|
||||
if ! [[ -f server.pem ]];then
|
||||
./server.sh
|
||||
fi
|
||||
|
||||
if ! [[ -f client.pem ]];then
|
||||
./client.sh
|
||||
fi
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"CN": "client",
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "CN",
|
||||
"L": "BeiJing",
|
||||
"O": "Bio",
|
||||
"OU": "MyTeam",
|
||||
"ST": "BeiJing"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
cfssl gencert \
|
||||
-ca=ca.pem \
|
||||
-ca-key=ca-key.pem \
|
||||
-config=ca-config.json \
|
||||
-profile=client \
|
||||
client-csr.json | cfssljson -bare client
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"CN": "apiservver",
|
||||
"hosts": [
|
||||
"127.0.0.1",
|
||||
"*.bioos.com",
|
||||
"localhost",
|
||||
"bioos.com"
|
||||
],
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "CN",
|
||||
"L": "BeiJing",
|
||||
"O": "Bio",
|
||||
"OU": "MyTeam",
|
||||
"ST": "BeiJing"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
cfssl gencert \
|
||||
-ca=ca.pem \
|
||||
-ca-key=ca-key.pem \
|
||||
-config=ca-config.json \
|
||||
-profile=server \
|
||||
server-csr.json | cfssljson -bare server
|
|
@ -0,0 +1,14 @@
|
|||
[request_definition]
|
||||
r = sub, obj, act
|
||||
|
||||
[policy_definition]
|
||||
p = sub, obj, act
|
||||
|
||||
[role_definition]
|
||||
g = _, _
|
||||
|
||||
[policy_effect]
|
||||
e = some(where (p.eft == allow))
|
||||
|
||||
[matchers]
|
||||
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.sub == "admin"
|
|
@ -0,0 +1,3 @@
|
|||
p, user1, proto.WorkspaceService, GetWorkspace
|
||||
p, user1, Workspace, Create
|
||||
p, user1, Workspace, List
|
|
|
@ -0,0 +1,63 @@
|
|||
version: '3'
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: mypassword
|
||||
MYSQL_DATABASE: bioos
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql-storage:/var/lib/mysql
|
||||
networks:
|
||||
- bioos
|
||||
apiserver:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: build/apiserver/Dockerfile
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_HOST: mysql
|
||||
MYSQL_USERNAME: root
|
||||
MYSQL_PASSWORD: mypassword
|
||||
MYSQL_PORT: "3306"
|
||||
MYSQL_DB: bioos
|
||||
command: ["--config", "/app/conf/apiserver-mysql.yaml", "--log-level", "debug"]
|
||||
ports:
|
||||
- "8888:8888"
|
||||
- "50051:50051"
|
||||
volumes:
|
||||
- bioos-storage:/app/conf/fs
|
||||
depends_on:
|
||||
- mysql
|
||||
networks:
|
||||
- bioos
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: build/web/Dockerfile
|
||||
restart: always
|
||||
depends_on:
|
||||
- apiserver
|
||||
ports:
|
||||
- "8901:80"
|
||||
networks:
|
||||
- bioos
|
||||
cromwell:
|
||||
image: broadinstitute/cromwell:85
|
||||
restart: always
|
||||
command:
|
||||
- server
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- bioos-storage:/nfs/bioos-storage
|
||||
networks:
|
||||
- bioos
|
||||
networks:
|
||||
bioos:
|
||||
driver: bridge
|
||||
volumes:
|
||||
bioos-storage:
|
||||
mysql-storage:
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
Binary file not shown.
After Width: | Height: | Size: 283 KiB |
Binary file not shown.
After Width: | Height: | Size: 132 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,140 @@
|
|||
//
|
||||
// Copyright 2023 Beijing Volcano Engine Technology Ltd.
|
||||
// Copyright 2023 Guangzhou Laboratory
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/credentials/oauth"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
|
||||
pb "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultName = ""
|
||||
)
|
||||
|
||||
var (
|
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
name = flag.String("name", defaultName, "Name to query")
|
||||
certFile = flag.String("cert-file", "", "cert file name")
|
||||
keyFile = flag.String("key-file", "", "key file name")
|
||||
serverCertFile = flag.String("server-cert-file", "", "server cert file name")
|
||||
caFile = flag.String("ca-file", "", "ca file name")
|
||||
serverName = flag.String("server-name", "", "server name")
|
||||
username = flag.String("username", "", "username")
|
||||
password = flag.String("password", "", "password")
|
||||
accessToken = flag.String("access-token", "", "oauth access token")
|
||||
)
|
||||
|
||||
type basicAuth struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func (b basicAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
|
||||
auth := b.username + ":" + b.password
|
||||
enc := base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
return map[string]string{
|
||||
"authorization": "Basic " + enc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b basicAuth) RequireTransportSecurity() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
creds := insecure.NewCredentials()
|
||||
if *serverName != "" {
|
||||
if *serverCertFile != "" {
|
||||
var err error
|
||||
creds, err = credentials.NewClientTLSFromFile(*serverCertFile, *serverName)
|
||||
if err != nil {
|
||||
grpclog.Fatalf("Failed to create TLS credentials %v", err)
|
||||
}
|
||||
} else if *certFile != "" && *keyFile != "" {
|
||||
cert, err := tls.LoadX509KeyPair(*certFile, *keyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("load cert err: %v", err)
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
if *caFile != "" {
|
||||
ca, _ := os.ReadFile(*caFile)
|
||||
certPool.AppendCertsFromPEM(ca)
|
||||
}
|
||||
|
||||
creds = credentials.NewTLS(&tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ServerName: *serverName,
|
||||
RootCAs: certPool,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var auth credentials.PerRPCCredentials
|
||||
|
||||
grpcOpts := []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor),
|
||||
grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor),
|
||||
grpc.WithTransportCredentials(creds),
|
||||
}
|
||||
if *username != "" && *password != "" {
|
||||
fmt.Printf("using basic auth: %s:%s\n", *username, *password)
|
||||
auth = &basicAuth{
|
||||
username: *username,
|
||||
password: *password,
|
||||
}
|
||||
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(auth))
|
||||
} else if *accessToken != "" {
|
||||
auth = oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: *accessToken})}
|
||||
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(auth))
|
||||
}
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.Dial(*addr, grpcOpts...)
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c := pb.NewWorkspaceServiceClient(conn)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
r, err := c.GetWorkspace(ctx, &pb.GetWorkspaceRequest{Id: *name})
|
||||
if err != nil {
|
||||
log.Fatalf("could not get workspace: %v", err)
|
||||
}
|
||||
log.Printf("Workspace: %s", r.GetWorkspace())
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# your commits will be validated after saving and closing the commit message in your text editor.
|
||||
# install go-gitlint via `go install github.com/llorllale/go-gitlint@latest`
|
||||
go-gitlint --msg-file="$1"
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
LC_ALL=C
|
||||
|
||||
local_branch="$(git rev-parse --abbrev-ref HEAD)"
|
||||
|
||||
valid_branch_regex="^(main|develop)$|(feature|feat|release|hotfix)\/[A-Za-z0-9._-]+$|^HEAD$"
|
||||
|
||||
message="Branch names should match the regular express: $valid_branch_regex.
|
||||
Rename your branch to a valid name by: git branch -m oldName newName."
|
||||
|
||||
if [[ ! $local_branch =~ $valid_branch_regex ]]
|
||||
then
|
||||
echo "$message"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd web
|
||||
npx lint-staged
|
|
@ -0,0 +1,205 @@
|
|||
module github.com/Bio-OS/bioos
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/casbin/casbin/v2 v2.65.2
|
||||
github.com/casbin/gorm-adapter/v3 v3.15.0
|
||||
github.com/cloudwego/hertz v0.6.3
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-git/go-git/v5 v5.6.1
|
||||
github.com/go-kratos/kratos/v2 v2.6.1
|
||||
github.com/go-playground/validator/v10 v10.12.0
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/hertz-contrib/cors v0.0.0-20230307061136-1fe747ea6a2a
|
||||
github.com/hertz-contrib/http2 v0.1.6
|
||||
github.com/hertz-contrib/requestid v1.1.0
|
||||
github.com/hertz-contrib/swagger v0.0.0-20220711030440-b6402d4709f0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/jarcoal/httpmock v1.3.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.27.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/shaj13/go-guardian/v2 v2.11.5
|
||||
github.com/shaj13/libcache v1.0.0
|
||||
github.com/spf13/cast v1.5.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
|
||||
github.com/swaggo/swag v1.8.12
|
||||
github.com/vinllen/mgo v0.0.0-20220329061231-e5ecea62f194
|
||||
go.mongodb.org/mongo-driver v1.8.3
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/sync v0.1.0
|
||||
google.golang.org/grpc v1.52.0-dev
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.4.7
|
||||
gorm.io/driver/sqlite v1.4.4
|
||||
gorm.io/gorm v1.24.6
|
||||
k8s.io/api v0.26.1
|
||||
k8s.io/apimachinery v0.26.1
|
||||
k8s.io/client-go v0.26.1
|
||||
k8s.io/component-base v0.26.1
|
||||
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448
|
||||
sigs.k8s.io/controller-runtime v0.14.5
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.8.0 // indirect
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/bytedance/go-tagexpr/v2 v2.9.2 // indirect
|
||||
github.com/bytedance/gopkg v0.0.0-20220817015305-b879a72dc90f // indirect
|
||||
github.com/bytedance/sonic v1.8.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/cloudflare/circl v1.3.2 // indirect
|
||||
github.com/cloudwego/netpoll v0.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/fatih/color v1.14.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.19.1 // indirect
|
||||
github.com/glebarez/sqlite v1.5.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/spec v0.20.8 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.5.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/henrylee2cn/ameda v1.4.10 // indirect
|
||||
github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.12.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.17.2 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.14.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.2 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.10 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/microsoft/go-mssqldb v0.17.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/nyaruka/phonenumbers v1.0.56 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tidwall/gjson v1.14.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.2 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/arch v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220804142021-4e6b2dfa6612 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gorm.io/driver/postgres v1.4.4 // indirect
|
||||
gorm.io/driver/sqlserver v1.4.1 // indirect
|
||||
gorm.io/plugin/dbresolver v1.3.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.26.1 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.19.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/apache/thrift => github.com/apache/thrift v0.13.0
|
||||
|
||||
exclude cloud.google.com/go/compute v1.9.0
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
Copyright 2023 Beijing Volcano Engine Technology Ltd.
|
||||
Copyright 2023 Guangzhou Laboratory
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,33 @@
|
|||
SHELL := /bin/bash
|
||||
|
||||
# include the common make file
|
||||
COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
|
||||
|
||||
ifeq ($(origin ROOT_DIR),undefined)
|
||||
ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/../.. && pwd -P))
|
||||
endif
|
||||
ifeq ($(origin OUTPUT_DIR),undefined)
|
||||
OUTPUT_DIR := _output
|
||||
$(shell mkdir -p $(OUTPUT_DIR))
|
||||
endif
|
||||
GIT_COMMIT:=$(shell git rev-parse HEAD)
|
||||
|
||||
# Copy githook scripts when execute makefile
|
||||
COPY_GITHOOK:=$(shell cp -f githooks/* .git/hooks/)
|
||||
|
||||
PLATFORMS ?= darwin_amd64 adrwin_arm64 windows_amd64 linux_amd64 linux_arm64
|
||||
# Set a specific PLATFORM
|
||||
ifeq ($(origin PLATFORM), undefined)
|
||||
ifeq ($(origin GOOS), undefined)
|
||||
GOOS := $(shell go env GOOS)
|
||||
endif
|
||||
ifeq ($(origin GOARCH), undefined)
|
||||
GOARCH := $(shell go env GOARCH)
|
||||
endif
|
||||
PLATFORM := $(GOOS)_$(GOARCH)
|
||||
IMAGE_PLAT := linux_amd64
|
||||
else
|
||||
GOOS := $(word 1, $(subst _, ,$(PLATFORM)))
|
||||
GOARCH := $(word 2, $(subst _, ,$(PLATFORM)))
|
||||
IMAGE_PLAT := $(PLATFORM)
|
||||
endif
|
|
@ -0,0 +1,17 @@
|
|||
.PHONY: gen.add-copyright
|
||||
gen.add-copyright:
|
||||
@addlicense -v -f $(ROOT_DIR)/hack/boilerplate/boilerplate.txt \
|
||||
--ignore "build/**" \
|
||||
--ignore "conf/**" \
|
||||
--ignore "docker-compose.yaml" \
|
||||
--ignore "docs/**" \
|
||||
--ignore "internal/**" \
|
||||
--ignore "third_party/**" \
|
||||
--ignore "vendor/**" \
|
||||
--ignore "web/node_modules/**" \
|
||||
--ignore "web/*.js" \
|
||||
--ignore "web/**/nbv.js" \
|
||||
--ignore "web/**/prism.js" \
|
||||
--ignore ".golangci.yaml" \
|
||||
--ignore ".idea/**" \
|
||||
--ignore ".git/**" .
|
|
@ -0,0 +1,63 @@
|
|||
GO := go
|
||||
GO_SUPPORTED_VERSIONS ?= 1.13|1.14|1.15|1.16|1.17|1.18|1.19|1.20
|
||||
GO_LDFLAGS += -X $(ROOT_PACKAGE)/pkg/version.Version=$(VERSION) \
|
||||
-X $(ROOT_PACKAGE)/pkg/version.Branch=$(GIT_BRANCH) \
|
||||
-X $(ROOT_PACKAGE)/pkg/version.GitCommit=$(GIT_COMMIT) \
|
||||
-X $(ROOT_PACKAGE)/pkg/version.GitTreeState=$(GIT_TREE_STATE) \
|
||||
-X $(ROOT_PACKAGE)/pkg/version.GitBranch=$(GIT_BRANCH) \
|
||||
-X $(ROOT_PACKAGE)/pkg/version.BuildTime=$(shell date +'%Y-%m-%dT%H:%M:%SZ')
|
||||
ifneq ($(DLV),)
|
||||
GO_BUILD_FLAGS += -gcflags "all=-N -l"
|
||||
LDFLAGS = ""
|
||||
endif
|
||||
GO_BUILD_FLAGS += -ldflags "$(GO_LDFLAGS)"
|
||||
|
||||
COMMANDS ?= $(wildcard ${ROOT_DIR}/cmd/*)
|
||||
BINS ?= $(foreach cmd,${COMMANDS},$(notdir ${cmd}))
|
||||
|
||||
|
||||
.PHONY: go.tidy
|
||||
go.tidy:
|
||||
@$(GO) mod tidy
|
||||
|
||||
.PHONY: go.build.verify
|
||||
go.build.verify:
|
||||
@echo "===========> verify go"
|
||||
@if ! which go &>/dev/null; then echo "Cannot found go compile tool. Please install go tool first."; exit 1; fi
|
||||
|
||||
|
||||
.PHONY: go.build
|
||||
go.build: go.build.verify go.tidy $(addprefix go.build., $(addprefix $(PLATFORM)., $(BINS)))
|
||||
|
||||
|
||||
.PHONY: go.build.%
|
||||
go.build.%:
|
||||
$(eval COMMAND := $(word 2,$(subst ., ,$*)))
|
||||
$(eval PLATFORM := $(word 1,$(subst ., ,$*)))
|
||||
$(eval OS := $(word 1,$(subst _, ,$(PLATFORM))))
|
||||
$(eval ARCH := $(word 2,$(subst _, ,$(PLATFORM))))
|
||||
@echo "===========> Building binary $(COMMAND) $(VERSION) for $(OS) $(ARCH)"
|
||||
@echo "GOOS=$(OS) GOARCH=$(ARCH) $(GO) build $(GO_BUILD_FLAGS) -o $(OUTPUT_DIR)/platforms/$(OS)/$(ARCH)/$(COMMAND)$(GO_OUT_EXT) $(ROOT_PACKAGE)/cmd/$(COMMAND)"
|
||||
@GOOS=$(OS) GOARCH=$(ARCH) $(GO) build $(GO_BUILD_FLAGS) -o $(OUTPUT_DIR)/platforms/$(OS)/$(ARCH)/$(COMMAND)$(GO_OUT_EXT) $(ROOT_PACKAGE)/cmd/$(COMMAND)
|
||||
|
||||
.PHONY: go.lint
|
||||
go.lint: tools.verify.golangci-lint
|
||||
@echo "===========> Run golangci-lint"
|
||||
@golangci-lint run -c $(ROOT_DIR)/.golangci.yaml $(ROOT_DIR)/...
|
||||
|
||||
.PHONY: go.run
|
||||
go.run: tools.install.womtool generate.certs $(addprefix go.run., $(addprefix $(PLATFORM)., apiserver))
|
||||
|
||||
.PHONY: generate.certs
|
||||
generate.certs:
|
||||
@echo "===========> Generating certs"
|
||||
@bash ${ROOT_DIR}/conf/certs/certs.sh
|
||||
|
||||
.PHONY: go.run.%
|
||||
go.run.%:
|
||||
$(eval COMMAND := $(word 2,$(subst ., ,$*)))
|
||||
$(eval PLATFORM := $(word 1,$(subst ., ,$*)))
|
||||
$(eval OS := $(word 1,$(subst _, ,$(PLATFORM))))
|
||||
$(eval ARCH := $(word 2,$(subst _, ,$(PLATFORM))))
|
||||
@echo "===========> Running binary $(COMMAND) $(VERSION) for $(OS) $(ARCH)"
|
||||
@$(OUTPUT_DIR)/platforms/$(OS)/$(ARCH)/$(COMMAND)$(GO_OUT_EXT) --config conf/apiserver.yaml
|
|
@ -0,0 +1,22 @@
|
|||
.PHONY: protoc.verify
|
||||
protoc.verify:
|
||||
@echo "===========> verify protoc"
|
||||
@if ! which protoc &>/dev/null; then echo "Cannot found protoc compile tool. Please install protoc tool first."; exit 1; fi
|
||||
@if ! which protoc-gen-go &>/dev/null; then echo "Cannot found protoc-gen-go. Please install protoc-gen-go tool first."; exit 1; fi
|
||||
@if ! which protoc-gen-go-grpc &>/dev/null; then echo "Cannot found protoc-gen-go-grpc tool. Please install protoc-gen-go-grpc tool first."; exit 1; fi
|
||||
@if ! which protoc-gen-go-errors &>/dev/null; then echo "Cannot found protoc-gen-go-errors tool. Please install protoc-gen-go-errors tool first."; exit 1; fi
|
||||
|
||||
.PHONY: protoc.gen
|
||||
protoc.gen: protoc.verify tools.verify
|
||||
@echo "===========> gen grpc code"
|
||||
@protoc --proto_path=. \
|
||||
--proto_path=./third_party \
|
||||
--go_out=. \
|
||||
--go_opt=paths=source_relative \
|
||||
--go-grpc_out=. \
|
||||
--go-grpc_opt=paths=source_relative \
|
||||
--go-errors_out=paths=source_relative:. \
|
||||
--go-http_out=paths=source_relative:. \
|
||||
internal/context/workspace/interface/grpc/proto/*.proto \
|
||||
internal/context/submission/interface/grpc/proto/*.proto \
|
||||
internal/context/notebookserver/interface/grpc/proto/*.proto
|
|
@ -0,0 +1,55 @@
|
|||
DOCKER := docker
|
||||
IMAGES_DIR ?= $(wildcard ${ROOT_DIR}/build/*)
|
||||
IMAGES ?= $(foreach image,${IMAGES_DIR},$(notdir ${image}))
|
||||
IMAGE_REGISTRY ?= quay.io/bioos
|
||||
|
||||
.PHONY: image.verify
|
||||
image.verify:
|
||||
@echo "===========> verify docker"
|
||||
@if ! which docker &>/dev/null; then echo "Cannot found docker. Please install docker first."; exit 1; fi
|
||||
@if ! docker ps &>/dev/null; then echo "Docker daemon not running. Please start it first.";exit 1;fi
|
||||
|
||||
.PHONY: buildx.verify
|
||||
buildx.verify:
|
||||
@echo "===========> verify docker buildx"
|
||||
@if ! docker buildx version &>/dev/null; then echo "Cannot found docker buildx. Please install docker buildx first. Link: https://docs.docker.com/build/architecture/"; exit 1; fi
|
||||
|
||||
.PHONY: image.build
|
||||
image.build: image.verify $(addprefix image.build., $(addprefix $(IMAGE_PLAT)., $(IMAGES)))
|
||||
|
||||
.PHONY: image.buildx.multiarch.%
|
||||
image.buildx.multiarch.%: image.verify buildx.verify
|
||||
$(eval IMAGE := $(*))
|
||||
@echo "===========> Building multiple arch docker image $(IMAGE)"
|
||||
$(DOCKER) buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-t $(IMAGE_REGISTRY)/$(IMAGE):$(VERSION) \
|
||||
-f $(ROOT_DIR)/build/$(IMAGE)/Dockerfile . --push
|
||||
|
||||
.PHONY: image.buildx
|
||||
image.buildx: image.verify $(foreach i,$(IMAGES), $(addprefix image.buildx.multiarch.,$(i)))
|
||||
|
||||
.PHONY: image.build.%
|
||||
image.build.%:
|
||||
$(eval IMAGE := $(word 2,$(subst ., ,$*)))
|
||||
$(eval PLATFORM := $(word 1,$(subst ., ,$*)))
|
||||
$(eval OS := $(word 1,$(subst _, ,$(PLATFORM))))
|
||||
$(eval ARCH := $(word 2,$(subst _, ,$(PLATFORM))))
|
||||
@echo "===========> Building docker image $(IMAGE) for $(OS) $(ARCH)"
|
||||
DOCKER_BUILDKIT=1 $(DOCKER) build \
|
||||
--platform $(OS)/$(ARCH) \
|
||||
-t $(IMAGE_REGISTRY)/$(IMAGE):$(VERSION) \
|
||||
-f $(ROOT_DIR)/build/$(IMAGE)/Dockerfile .
|
||||
|
||||
.PHONY: image.push
|
||||
image.push: image.build $(addprefix image.push., $(addprefix $(IMAGE_PLAT)., $(IMAGES)))
|
||||
|
||||
.PHONY: image.push.%
|
||||
image.push.%:
|
||||
$(eval IMAGE := $(word 2,$(subst ., ,$*)))
|
||||
$(eval PLATFORM := $(word 1,$(subst ., ,$*)))
|
||||
$(eval OS := $(word 1,$(subst _, ,$(PLATFORM))))
|
||||
$(eval ARCH := $(word 2,$(subst _, ,$(PLATFORM))))
|
||||
@echo "===========> Pushing docker image $(IMAGE) for $(OS) $(ARCH)"
|
||||
@echo "$(DOCKER) push $(IMAGE_REGISTRY)/$(IMAGE):$(VERSION)"
|
||||
$(DOCKER) push $(IMAGE_REGISTRY)/$(IMAGE):$(VERSION)
|
|
@ -0,0 +1,11 @@
|
|||
.PHONY: swagger.gen
|
||||
swagger.gen: install.swagger
|
||||
@echo "===========> Generating swag API docs"
|
||||
@swag init --parseDependency --dir ./cmd/apiserver,./internal -g apiserver.go
|
||||
.PHONY: swagger.fmt
|
||||
swagger.fmt: install.swagger
|
||||
@echo "===========> Format swag comments"
|
||||
@swag fmt --dir ./cmd/apiserver,./internal
|
||||
|
||||
.PHONY: swagger.run
|
||||
swagger.run: swagger.gen swagger.fmt
|
|
@ -0,0 +1,68 @@
|
|||
TOOLS ?= swagger addlicense cfssl mockgen gotests git-chglog protoc-gen-go protoc-gen-go-grpc protoc-gen-go-http protoc-gen-go-errors go-gitlint golangci-lint
|
||||
|
||||
.PHONY: tools.install
|
||||
tools.install: $(addprefix tools.install., $(TOOLS))
|
||||
|
||||
.PHONY: tools.verify
|
||||
tools.verify: $(addprefix tools.verify., $(TOOLS))
|
||||
|
||||
.PHONY: tools.install.%
|
||||
tools.install.%:
|
||||
@echo "===========> Installing $*"
|
||||
@$(MAKE) install.$*
|
||||
|
||||
.PHONY: tools.verify.%
|
||||
tools.verify.%:
|
||||
@if ! which $* &>/dev/null; then $(MAKE) tools.install.$*; fi
|
||||
|
||||
.PHONY: install.swagger
|
||||
install.swagger:
|
||||
@$(GO) install github.com/swaggo/swag/cmd/swag@v1.8.12
|
||||
|
||||
.PHONY: install.cfssl
|
||||
install.cfssl:
|
||||
@$(GO) install github.com/cloudflare/cfssl/cmd/...@latest
|
||||
|
||||
.PHONY: install.go-gitlint
|
||||
install.go-gitlint:
|
||||
@$(GO) install github.com/llorllale/go-gitlint@latest
|
||||
|
||||
.PHONY: install.git-chglog
|
||||
install.git-chglog:
|
||||
@$(GO) install github.com/git-chglog/git-chglog/cmd/git-chglog@latest
|
||||
|
||||
.PHONY: install.mockgen
|
||||
install.mockgen:
|
||||
@$(GO) install github.com/golang/mock/mockgen@latest
|
||||
|
||||
.PHONY: install.gotests
|
||||
install.gotests:
|
||||
@$(GO) install github.com/cweill/gotests/gotests@latest
|
||||
|
||||
.PHONY: install.addlicense
|
||||
install.addlicense:
|
||||
@$(GO) install github.com/google/addlicense@latest
|
||||
|
||||
.PHONY: install.protoc-gen-go
|
||||
install.protoc-gen-go:
|
||||
@$(GO) install google.golang.org/protobuf/cmd/protoc-gen-go@v1.30.0
|
||||
|
||||
.PHONY: install.protoc-gen-go-grpc
|
||||
install.protoc-gen-go-grpc:
|
||||
@$(GO) install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0
|
||||
|
||||
.PHONY: install.protoc-gen-go-http
|
||||
install.protoc-gen-go-http:
|
||||
@$(GO) install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest
|
||||
|
||||
.PHONY: install.protoc-gen-go-errors
|
||||
install.protoc-gen-go-errors:
|
||||
@$(GO) install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest
|
||||
|
||||
.PHONY: install.golangci-lint
|
||||
install.golangci-lint:
|
||||
@$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
.PHONY: install.womtool
|
||||
install.womtool:
|
||||
@wget --progress=dot -cP ${ROOT_DIR} -O womtool.jar https://github.com/broadinstitute/cromwell/releases/download/85/womtool-85.jar
|
|
@ -0,0 +1,78 @@
|
|||
package apiserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"os"
|
||||
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
|
||||
grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
|
||||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/health"
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/apiserver/options"
|
||||
middlewaregrpc "github.com/Bio-OS/bioos/pkg/middlewares/grpc"
|
||||
appserver "github.com/Bio-OS/bioos/pkg/server"
|
||||
)
|
||||
|
||||
func setupGrpcServer(opts *options.Options, registers ...appserver.GRPCRegister) (*grpc.Server, error) {
|
||||
serverOptions := []grpc.ServerOption{
|
||||
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
|
||||
grpc_ctxtags.StreamServerInterceptor(),
|
||||
grpc_opentracing.StreamServerInterceptor(),
|
||||
grpc_prometheus.StreamServerInterceptor,
|
||||
middlewaregrpc.NewAuthStreamServerInterceptor(),
|
||||
grpc_recovery.StreamServerInterceptor(),
|
||||
middlewaregrpc.RBACStreamServerChain(),
|
||||
)),
|
||||
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
|
||||
grpc_ctxtags.UnaryServerInterceptor(),
|
||||
grpc_opentracing.UnaryServerInterceptor(),
|
||||
grpc_prometheus.UnaryServerInterceptor,
|
||||
middlewaregrpc.NewAuthUnaryServerInterceptor(),
|
||||
grpc_recovery.UnaryServerInterceptor(),
|
||||
middlewaregrpc.RBACUnaryServerChain(),
|
||||
)),
|
||||
}
|
||||
if opts.ServerOption.Grpc.TLS {
|
||||
tlsCredentials, err := loadTLSCredentials(opts.ServerOption.CertFile, opts.ServerOption.KeyFile, opts.ServerOption.CaFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverOptions = append(serverOptions, grpc.Creds(tlsCredentials))
|
||||
}
|
||||
grpcServer := grpc.NewServer(serverOptions...)
|
||||
for _, r := range registers {
|
||||
r(grpcServer)
|
||||
}
|
||||
reflection.Register(grpcServer)
|
||||
grpc_prometheus.Register(grpcServer)
|
||||
hs := health.NewServer()
|
||||
hs.SetServingStatus("grpc.health.v1.workspaceservice", 1)
|
||||
healthpb.RegisterHealthServer(grpcServer, hs)
|
||||
return grpcServer, nil
|
||||
}
|
||||
|
||||
func loadTLSCredentials(certFile, keyFile, caFile string) (credentials.TransportCredentials, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
ca, err := os.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool.AppendCertsFromPEM(ca)
|
||||
return credentials.NewTLS(&tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.VerifyClientCertIfGiven,
|
||||
ClientCAs: certPool,
|
||||
}), nil
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package apiserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/cloudwego/hertz/pkg/app/server"
|
||||
"github.com/cloudwego/hertz/pkg/common/config"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hertz-contrib/cors"
|
||||
"github.com/hertz-contrib/http2/factory"
|
||||
"github.com/hertz-contrib/requestid"
|
||||
"github.com/hertz-contrib/swagger"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/apiserver/options"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
apperrors "github.com/Bio-OS/bioos/pkg/errors"
|
||||
apphertz "github.com/Bio-OS/bioos/pkg/middlewares/hertz"
|
||||
"github.com/Bio-OS/bioos/pkg/notebook"
|
||||
appserver "github.com/Bio-OS/bioos/pkg/server"
|
||||
"github.com/Bio-OS/bioos/pkg/utils"
|
||||
"github.com/Bio-OS/bioos/pkg/version"
|
||||
)
|
||||
|
||||
func setupHTTPServer(opts *options.Options, registers ...appserver.RouteRegister) (*server.Hertz, error) {
|
||||
serverOptions := []config.Option{
|
||||
server.WithHostPorts(fmt.Sprintf(":%s", opts.ServerOption.Http.Port)),
|
||||
server.WithMaxRequestBodySize(opts.ServerOption.Http.MaxRequestBodySize),
|
||||
server.WithALPN(true),
|
||||
}
|
||||
|
||||
if opts.ServerOption.Http.TLS {
|
||||
cert, err := tls.LoadX509KeyPair(opts.ServerOption.CertFile, opts.ServerOption.KeyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to load x509 pair: %w", err)
|
||||
}
|
||||
certBytes, err := os.ReadFile(opts.ServerOption.CaFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read ca file: %w", err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok := caCertPool.AppendCertsFromPEM(certBytes)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to parse ca file: %w", err)
|
||||
}
|
||||
// set server tls.Config
|
||||
cfg := &tls.Config{
|
||||
// add certificate
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
// enable client authentication
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
ClientCAs: caCertPool,
|
||||
// cipher suites supported
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
},
|
||||
// set application protocol http2
|
||||
NextProtos: []string{http2.NextProtoTLS},
|
||||
}
|
||||
serverOptions = append(serverOptions, server.WithTLS(cfg))
|
||||
}
|
||||
httpServer := server.Default(serverOptions...)
|
||||
|
||||
httpServer.AddProtocol(http2.NextProtoTLS, factory.NewServerFactory())
|
||||
setupMiddlewares(httpServer)
|
||||
setupRouter(httpServer, opts)
|
||||
for _, r := range registers {
|
||||
r.AddRoute(httpServer)
|
||||
}
|
||||
return httpServer, nil
|
||||
}
|
||||
|
||||
func setupMiddlewares(h *server.Hertz) {
|
||||
h.Use(
|
||||
cors.New(cors.Config{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{"PUT", "PATCH", "OPTIONS", "GET", "POST", "DELETE"},
|
||||
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"},
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
AllowCredentials: true,
|
||||
AllowWebSockets: true,
|
||||
AllowWildcard: true,
|
||||
MaxAge: 12 * time.Hour,
|
||||
}),
|
||||
requestid.New(
|
||||
requestid.WithGenerator(func(ctx context.Context, c *app.RequestContext) string {
|
||||
return uuid.New().String()
|
||||
}),
|
||||
// set custom header for request id
|
||||
requestid.WithCustomHeaderStrKey(consts.XRequestIDKey),
|
||||
),
|
||||
apphertz.Logger(),
|
||||
)
|
||||
}
|
||||
|
||||
func setupRouter(h *server.Hertz, opts *options.Options) {
|
||||
h.GET("/ping", PingHandler)
|
||||
h.GET("/version", VersionHandler)
|
||||
url := swagger.URL("/swagger/doc.json") // The url pointing to API definition
|
||||
h.GET("/swagger/*any", swagger.WrapHandler(swaggerFiles.Handler, url))
|
||||
h.GET("/.well-known/configuration", clientConfigHandler(opts))
|
||||
|
||||
h.NoRoute(func(ctx context.Context, c *app.RequestContext) {
|
||||
utils.WriteHertzErrorResponse(c, &apperrors.AppError{Code: apperrors.RouteNotFoundCode, Message: "route not found"})
|
||||
})
|
||||
}
|
||||
|
||||
type clientConfig struct {
|
||||
Storage struct {
|
||||
FSPath []string `json:"fsPath,omitempty"`
|
||||
} `json:"storage"`
|
||||
Notebook struct {
|
||||
OfficialImages []notebook.Image `json:"officialImages"`
|
||||
ResourceOptions []notebook.ResourceSize `json:"resourceOptions"`
|
||||
} `json:"notebook"`
|
||||
}
|
||||
|
||||
// GetClientConfig get client configuration
|
||||
//
|
||||
// @Summary use to get client configuration
|
||||
// @Description get client configuration
|
||||
// @Router /.well-known/configuration [get]
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} clientConfig
|
||||
// @Failure 401 {object} apperrors.AppError "unauthorized"
|
||||
// @Failure 403 {object} apperrors.AppError "forbidden"
|
||||
// @Failure 500 {object} apperrors.AppError "internal system error"
|
||||
func clientConfigHandler(opts *options.Options) app.HandlerFunc {
|
||||
return func(_ context.Context, ctx *app.RequestContext) {
|
||||
var resp clientConfig
|
||||
if opts.StorageOption != nil {
|
||||
if opts.StorageOption.FileSystem != nil {
|
||||
// string array is for multi share storage porpose in future
|
||||
resp.Storage.FSPath = []string{opts.StorageOption.FileSystem.RootPath}
|
||||
}
|
||||
}
|
||||
resp.Notebook.OfficialImages = opts.NotebookOption.OfficialImages
|
||||
resp.Notebook.ResourceOptions = opts.NotebookOption.ListResourceSizes()
|
||||
utils.WriteHertzOKResponse(ctx, &resp)
|
||||
}
|
||||
}
|
||||
|
||||
// PingHandler ping handler
|
||||
//
|
||||
// @Summary ping
|
||||
// @Description ping
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /ping [get]
|
||||
// @Success 200
|
||||
func PingHandler(_ context.Context, ctx *app.RequestContext) {
|
||||
ctx.JSON(http.StatusOK, map[string]string{
|
||||
"ping": "pong",
|
||||
})
|
||||
}
|
||||
|
||||
// VersionHandler version handler
|
||||
//
|
||||
// @Summary version Summary
|
||||
// @Description version Description
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /version [get]
|
||||
// @Success 200
|
||||
func VersionHandler(_ context.Context, ctx *app.RequestContext) {
|
||||
ctx.JSON(http.StatusOK, version.Get())
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/context/submission/infrastructure/client/wes"
|
||||
"github.com/Bio-OS/bioos/pkg/auth"
|
||||
"github.com/Bio-OS/bioos/pkg/client"
|
||||
"github.com/Bio-OS/bioos/pkg/db"
|
||||
"github.com/Bio-OS/bioos/pkg/eventbus"
|
||||
"github.com/Bio-OS/bioos/pkg/log"
|
||||
"github.com/Bio-OS/bioos/pkg/notebook"
|
||||
"github.com/Bio-OS/bioos/pkg/server"
|
||||
"github.com/Bio-OS/bioos/pkg/storage"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigFlagName = "config"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
func init() {
|
||||
pflag.StringVarP(&cfgFile, ConfigFlagName, "c", cfgFile, "Read configuration from specified `FILE`, "+
|
||||
"support JSON, YAML formats.")
|
||||
cobra.OnInitialize(initConfig)
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetConfigName("apiserver") // name of config file (without extension)
|
||||
if cfgFile != "" { // enable ability to specify config file via flag
|
||||
viper.SetConfigFile(cfgFile)
|
||||
configDir := path.Dir(cfgFile)
|
||||
if configDir != "." {
|
||||
viper.AddConfigPath(configDir)
|
||||
}
|
||||
}
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
viper.AddConfigPath("conf")
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath("$HOME")
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
viper.WatchConfig()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
fmt.Println("Config file changed:", e.Name)
|
||||
})
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Client *client.Options `json:"client" mapstructure:"client"`
|
||||
ServerOption *server.Options `json:"server" mapstructure:"server"`
|
||||
LogOption *log.Options `json:"log" mapstructure:"log"`
|
||||
DBOption *db.Options `json:"db" mapstructure:"db"`
|
||||
AuthOption *auth.Options `json:"auth" mapstructure:"auth"`
|
||||
StorageOption *storage.Options `json:"storage" mapstructure:"storage"`
|
||||
EventBusOption *eventbus.Options `json:"eventBus" mapstructure:"eventBus"`
|
||||
WesOption *wes.Options `json:"wes" mapstructure:"wes"`
|
||||
NotebookOption *notebook.Options `json:"notebook" mapstructure:"notebook"`
|
||||
}
|
||||
|
||||
func NewOptions() *Options {
|
||||
return &Options{
|
||||
ServerOption: server.NewOptions(),
|
||||
LogOption: log.NewOptions(),
|
||||
DBOption: db.NewOptions(),
|
||||
AuthOption: auth.NewOptions(),
|
||||
StorageOption: storage.NewOptions(),
|
||||
EventBusOption: eventbus.NewOptions(),
|
||||
WesOption: wes.NewOptions(),
|
||||
NotebookOption: notebook.NewOptions(),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validate log options is valid.
|
||||
func (o *Options) Validate() error {
|
||||
if err := o.ServerOption.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.LogOption.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.DBOption.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.AuthOption.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.EventBusOption.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.StorageOption.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.Client.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.WesOption.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.NotebookOption.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options) AddFlags(fs *pflag.FlagSet) {
|
||||
o.ServerOption.AddFlags(fs)
|
||||
o.LogOption.AddFlags(fs)
|
||||
o.DBOption.AddFlags(fs)
|
||||
o.AuthOption.AddFlags(fs)
|
||||
o.StorageOption.AddFlags(fs)
|
||||
o.EventBusOption.AddFlags(fs)
|
||||
o.WesOption.AddFlags(fs)
|
||||
o.NotebookOption.AddFlags(fs)
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package apiserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"k8s.io/component-base/featuregate"
|
||||
|
||||
_ "github.com/Bio-OS/bioos/docs" // for swagger
|
||||
"github.com/Bio-OS/bioos/internal/apiserver/options"
|
||||
notebookserverapp "github.com/Bio-OS/bioos/internal/context/notebookserver/application"
|
||||
notebookservergrpc "github.com/Bio-OS/bioos/internal/context/notebookserver/interface/grpc"
|
||||
notebookserverproto "github.com/Bio-OS/bioos/internal/context/notebookserver/interface/grpc/proto"
|
||||
notebookserverhertz "github.com/Bio-OS/bioos/internal/context/notebookserver/interface/hertz"
|
||||
submissionapp "github.com/Bio-OS/bioos/internal/context/submission/application"
|
||||
submissiongrpc "github.com/Bio-OS/bioos/internal/context/submission/interface/grpc"
|
||||
submissionproto "github.com/Bio-OS/bioos/internal/context/submission/interface/grpc/proto"
|
||||
submissionhertz "github.com/Bio-OS/bioos/internal/context/submission/interface/hertz"
|
||||
workspaceapp "github.com/Bio-OS/bioos/internal/context/workspace/application"
|
||||
workspacegrpc "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc"
|
||||
workspaceproto "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
workspacehertz "github.com/Bio-OS/bioos/internal/context/workspace/interface/hertz"
|
||||
"github.com/Bio-OS/bioos/pkg/log"
|
||||
"github.com/Bio-OS/bioos/pkg/middlewares"
|
||||
"github.com/Bio-OS/bioos/pkg/notebook"
|
||||
"github.com/Bio-OS/bioos/pkg/server"
|
||||
"github.com/Bio-OS/bioos/pkg/version"
|
||||
)
|
||||
|
||||
const (
|
||||
component = "bioos-apiserver"
|
||||
)
|
||||
|
||||
func newBioosServerCommand(ctx context.Context, opts *options.Options) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: component,
|
||||
Short: "bioos apiserver",
|
||||
Long: `bioos apiserver
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := viper.BindPFlags(cmd.Flags()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(opts, viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToTimeHookFunc(time.RFC3339),
|
||||
notebook.ResourceQuantityStringToInt64HookFunc,
|
||||
))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := opts.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version.PrintVersionOrContinue()
|
||||
|
||||
log.RegisterLogger(opts.LogOption)
|
||||
defer log.Sync()
|
||||
|
||||
middlewares.RegisterAuthenticator(opts.AuthOption.AuthN)
|
||||
middlewares.RegisterAuthorizer(opts.AuthOption.AuthZ)
|
||||
|
||||
log.Infow("server options", "options", opts)
|
||||
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
log.Debugf("FLAG: --%s=%q", flag.Name, flag.Value)
|
||||
})
|
||||
|
||||
return run(ctx, opts)
|
||||
},
|
||||
Args: cobra.ExactArgs(0),
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context, opts *options.Options) error {
|
||||
log.Infow("called bioos-apiserver")
|
||||
|
||||
workspaceService, err := workspaceapp.NewWorkspaceService(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = workspaceService.Close(ctx)
|
||||
}()
|
||||
|
||||
workspaceGRPCService := workspacegrpc.NewServer(workspaceService)
|
||||
// TODO invoke workspace API by grpc service client after solved backend token
|
||||
notebookserverService, err := notebookserverapp.NewService(ctx, opts, workspaceGRPCService)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new notebook server service fail: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = notebookserverService.Close(ctx)
|
||||
}()
|
||||
|
||||
submissionService, err := submissionapp.NewSubmissionService(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = submissionService.Close(ctx)
|
||||
}()
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", opts.ServerOption.Grpc.Port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
workflowGRPCService := workspacegrpc.NewWorkflowServer(workspaceService)
|
||||
datamodelGRPCService := workspacegrpc.NewDataModelServer(workspaceService)
|
||||
notebookGRPCService := workspacegrpc.NewNotebookServer(workspaceService)
|
||||
submissionGRPCService := submissiongrpc.NewSubmissionServer(submissionService)
|
||||
versionGRPCService := workspacegrpc.NewVersionServer()
|
||||
notebookserverGRPCService := notebookservergrpc.NewServer(notebookserverService)
|
||||
grpcServer, err := setupGrpcServer(
|
||||
opts,
|
||||
server.GetGRPCRegister(workspaceproto.RegisterWorkspaceServiceServer, workspaceGRPCService),
|
||||
server.GetGRPCRegister(workspaceproto.RegisterWorkflowServiceServer, workflowGRPCService),
|
||||
server.GetGRPCRegister(workspaceproto.RegisterDataModelServiceServer, datamodelGRPCService),
|
||||
server.GetGRPCRegister(workspaceproto.RegisterNotebookServiceServer, notebookGRPCService),
|
||||
server.GetGRPCRegister(submissionproto.RegisterSubmissionServiceServer, submissionGRPCService),
|
||||
server.GetGRPCRegister(workspaceproto.RegisterVersionServiceServer, versionGRPCService),
|
||||
server.GetGRPCRegister(notebookserverproto.RegisterNotebookServerServiceServer, notebookserverGRPCService),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to setup grpc server: %v", err)
|
||||
}
|
||||
httpServer, err := setupHTTPServer(
|
||||
opts,
|
||||
workspacehertz.NewRouteRegister(workspaceService),
|
||||
submissionhertz.NewRouteRegister(submissionService),
|
||||
notebookserverhertz.NewRouteRegister(notebookserverService),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to setup http server: %v", err)
|
||||
}
|
||||
|
||||
eg := errgroup.Group{}
|
||||
eg.Go(func() error {
|
||||
err := grpcServer.Serve(lis)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
return err
|
||||
})
|
||||
eg.Go(func() error {
|
||||
httpServer.Spin()
|
||||
return nil
|
||||
})
|
||||
if err := eg.Wait(); err != nil {
|
||||
log.Fatalf("get error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewBioosServerCommand instance a bioos server command.
|
||||
func NewBioosServerCommand(ctx context.Context) *cobra.Command {
|
||||
opt := options.NewOptions()
|
||||
cmd := newBioosServerCommand(ctx, opt)
|
||||
|
||||
opt.AddFlags(cmd.Flags())
|
||||
featureGate := featuregate.NewFeatureGate()
|
||||
featureGate.AddFlag(cmd.Flags())
|
||||
version.AddFlags(cmd.Flags())
|
||||
cmd.Flags().AddFlag(pflag.Lookup(options.ConfigFlagName))
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package bioctl
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
|
||||
internalcmd "github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
clidatamodel "github.com/Bio-OS/bioos/internal/bioctl/cmd/data-model"
|
||||
clisubmission "github.com/Bio-OS/bioos/internal/bioctl/cmd/submission"
|
||||
cliversion "github.com/Bio-OS/bioos/internal/bioctl/cmd/version"
|
||||
cliworkflow "github.com/Bio-OS/bioos/internal/bioctl/cmd/workflow"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
)
|
||||
|
||||
// NewDefaultBIOCtlCommand return the bioctl cmd.
|
||||
func NewDefaultBIOCtlCommand() *cobra.Command {
|
||||
return NewBIOCtlCommandWithArgs(os.Stdin, os.Stdout, os.Stderr)
|
||||
}
|
||||
|
||||
// NewBIOCtlCommandWithArgs initialized a bioctl command.
|
||||
func NewBIOCtlCommandWithArgs(in io.Reader, out, err io.Writer) *cobra.Command {
|
||||
command := newBIOCtlCommand()
|
||||
command.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc)
|
||||
|
||||
flags := command.PersistentFlags()
|
||||
|
||||
addProfilingFlags(flags)
|
||||
command.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)
|
||||
|
||||
opt := clioptions.DefaultConfig
|
||||
|
||||
command.AddCommand(cliworkspace.NewCmdWorkspace(&opt))
|
||||
command.AddCommand(cliworkflow.NewCmdWorkflow(&opt))
|
||||
command.AddCommand(clidatamodel.NewCmdDataModel(&opt))
|
||||
command.AddCommand(clisubmission.NewCmdSubmission(&opt))
|
||||
addExample(command)
|
||||
|
||||
// version doesn't need Example text
|
||||
command.AddCommand(cliversion.NewCmdVersion(&opt))
|
||||
|
||||
command.PersistentFlags().AddFlag(pflag.Lookup(clioptions.ConfigFlagName))
|
||||
// read from config file
|
||||
clioptions.LoadConfigFile(clioptions.CfgFile, &opt)
|
||||
|
||||
clioptions.SetConfigEnv()
|
||||
|
||||
// define global options
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.ServerAddr, "server-addr", "SERVER_ADDR", "Bioos apiserver address")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.Insecure, "insecure", "INSECURE", "Whether enable tls")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.ServerCertFile, "server-cert-file", "SERVER_CERT_FILE", "Server cert file path")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.ServerName, "server-name", "SERVER_NAME", "Server Name")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.ClientCertFile, "client-cert-file", "CLIENT_CERT_FILE", "Client cert file path")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.ClientCertKeyFile, "client-cert-key-file", "CLIENT_CERT_KEY_FILE", "Client key file path")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.CaFile, "ca-file", "CA_FILE", "CA file path")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.Username, "username", "USERNAME", "Username")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.Password, "password", "PASSWORD", "Password")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.AuthToken, "auth-token", "AUTH_TOKEN", "Auth token")
|
||||
loadConfigFromOptionOrEnv(command, (*string)(&opt.Client.Method), "connect-method", "CONNECT_METHOD", "Cli connect method: grpc or http")
|
||||
loadConfigFromOptionOrEnv(command, &opt.Client.Timeout, "connect-timeout", "CONNECT_TIMEOUT", "Connect timeout seconds")
|
||||
|
||||
// format should only come from command line
|
||||
command.PersistentFlags().StringVarP((*string)(&opt.Stream.OutputFormat), "output-format", "o", string(opt.Stream.OutputFormat), "Cli output format: json, yaml, table or text")
|
||||
|
||||
_ = viper.BindPFlags(command.PersistentFlags())
|
||||
// read from environment
|
||||
|
||||
command.PersistentFlags().AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
var castFuncMap = map[reflect.Kind]interface{}{
|
||||
reflect.Bool: cast.ToBool,
|
||||
reflect.String: cast.ToString,
|
||||
reflect.Int: cast.ToInt,
|
||||
}
|
||||
|
||||
var pfFuncMap = map[reflect.Kind]interface{}{
|
||||
reflect.Bool: (*pflag.FlagSet).BoolVar,
|
||||
reflect.String: (*pflag.FlagSet).StringVar,
|
||||
reflect.Int: (*pflag.FlagSet).IntVar,
|
||||
}
|
||||
|
||||
// Read config from env or flag, flag > env
|
||||
func loadConfigFromOptionOrEnv(command *cobra.Command, opt interface{}, optName, envName, usage string) {
|
||||
optT := reflect.TypeOf(opt)
|
||||
for optT.Kind() == reflect.Pointer {
|
||||
optT = optT.Elem()
|
||||
}
|
||||
optKind := optT.Kind()
|
||||
viper.BindEnv(envName)
|
||||
eVal := viper.Get(envName)
|
||||
defaultVal := reflect.ValueOf(opt).Elem()
|
||||
if eVal != nil {
|
||||
defaultVal = reflect.ValueOf(castFuncMap[optKind]).Call([]reflect.Value{reflect.ValueOf(eVal)})[0]
|
||||
}
|
||||
reflect.ValueOf(pfFuncMap[optKind]).Call([]reflect.Value{
|
||||
reflect.ValueOf(command.PersistentFlags()),
|
||||
reflect.ValueOf(opt),
|
||||
reflect.ValueOf(optName),
|
||||
defaultVal,
|
||||
reflect.ValueOf(usage),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func newBIOCtlCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "bioctl",
|
||||
Short: "bioctl command",
|
||||
Long: `bioctl command`,
|
||||
Run: runHelp,
|
||||
PersistentPreRunE: func(*cobra.Command, []string) error {
|
||||
return initProfiling()
|
||||
},
|
||||
PersistentPostRunE: func(*cobra.Command, []string) error {
|
||||
return flushProfiling()
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func runHelp(cmd *cobra.Command, args []string) {
|
||||
_ = cmd.Help()
|
||||
}
|
||||
|
||||
func addExample(cmd *cobra.Command) {
|
||||
if !cmd.HasSubCommands() {
|
||||
if cmd.Example != "" {
|
||||
return
|
||||
}
|
||||
cmd.Example = internalcmd.GenExample(cmd)
|
||||
return
|
||||
}
|
||||
for _, subCommand := range cmd.Commands() {
|
||||
addExample(subCommand)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package data_model
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
func NewCmdDataModel(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "data-model",
|
||||
Short: "data-model command",
|
||||
Long: `data-model command`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: prompt.SelectSubCommand,
|
||||
}
|
||||
cmd.AddCommand(NewCmdImport(opt))
|
||||
cmd.AddCommand(NewCmdList(opt))
|
||||
cmd.AddCommand(NewCmdDelete(opt))
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package data_model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// DeleteOptions is an options to delete a data-model.
|
||||
type DeleteOptions struct {
|
||||
WorkspaceName string
|
||||
Name string
|
||||
|
||||
workspaceClient factory.WorkspaceClient
|
||||
dataModelClient factory.DataModelClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewDeleteOptions returns a reference to a DeleteOptions
|
||||
func NewDeleteOptions(opt *clioptions.GlobalOptions) *DeleteOptions {
|
||||
return &DeleteOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdDelete(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewDeleteOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "delete a data-model",
|
||||
Long: "delete a data-model",
|
||||
Args: cobra.NoArgs,
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringVarP(&o.Name, "name", "n", o.Name, "data-model names to List")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *DeleteOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.dataModelClient, err = f.DataModelClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the delete options
|
||||
func (o *DeleteOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the delete data-model command
|
||||
func (o *DeleteOptions) Run(args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &convert.ListDataModelsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
}
|
||||
|
||||
remnant := make([]string, 0)
|
||||
if o.Name != "" {
|
||||
req.SearchWord = o.Name
|
||||
remnant = []string{o.Name}
|
||||
}
|
||||
|
||||
listResp, err := o.dataModelClient.ListDataModels(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range listResp.Items {
|
||||
// Delete all data-models
|
||||
if o.Name == "" {
|
||||
_, err := o.dataModelClient.DeleteDataModel(ctx, &convert.DeleteDataModelRequest{
|
||||
ID: item.ID,
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
remnant = append(remnant, item.Name)
|
||||
}
|
||||
// Delete one specified data_model
|
||||
} else {
|
||||
if item.Name == o.Name {
|
||||
_, err := o.dataModelClient.DeleteDataModel(ctx, &convert.DeleteDataModelRequest{
|
||||
ID: item.ID,
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
} //remove specified data_model name from remnant
|
||||
remnant = []string{}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(remnant) > 0 {
|
||||
return fmt.Errorf("%v delete failed", remnant)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetPromptArgs() ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Name, err = prompt.PromptOptionalString("DataModel Name", prompt.WithInputMessage("all dataModel will be deleted if no name specified"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package data_model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
"github.com/Bio-OS/bioos/pkg/utils"
|
||||
)
|
||||
|
||||
// ImportOptions is an options to import a data-model.
|
||||
type ImportOptions struct {
|
||||
WorkspaceName string
|
||||
InputFile string
|
||||
|
||||
workspaceClient factory.WorkspaceClient
|
||||
dataModelClient factory.DataModelClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewImportOptions returns a reference to a ImportOptions
|
||||
func NewImportOptions(opt *clioptions.GlobalOptions) *ImportOptions {
|
||||
return &ImportOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdImport(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewImportOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "import a data-model",
|
||||
Long: "import a data-model",
|
||||
Args: cobra.NoArgs,
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringVarP(&o.InputFile, "input-file ", "i", o.InputFile, "the file (only support csv) to import")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *ImportOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.dataModelClient, err = f.DataModelClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the import options
|
||||
func (o *ImportOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
if o.InputFile == "" || path.Ext(o.InputFile) != ".csv" {
|
||||
return fmt.Errorf("you must choose a csv file to import")
|
||||
}
|
||||
_, err := os.Stat(o.InputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the import data-model command
|
||||
func (o *ImportOptions) Run(args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := strings.TrimSuffix(path.Base(o.InputFile), path.Ext(o.InputFile))
|
||||
|
||||
listResp, err := o.dataModelClient.ListDataModels(ctx, &convert.ListDataModelsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
Types: []string{consts.DataModelTypeEntity},
|
||||
SearchWord: name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range listResp.Items {
|
||||
if item.Name == name {
|
||||
_, err = o.dataModelClient.DeleteDataModel(ctx, &convert.DeleteDataModelRequest{
|
||||
ID: item.ID,
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete existed dataModel %s failed: %w", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req := &convert.PatchDataModelRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
headers, rows, err := utils.ReadDataModelFromCSV(o.InputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Headers = headers
|
||||
req.Rows = make([][]string, len(rows))
|
||||
for i, data := range rows {
|
||||
req.Rows[i] = data
|
||||
}
|
||||
|
||||
resp, err := o.dataModelClient.PatchDataModel(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter.Write(resp.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetPromptArgs() ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.InputFile, err = prompt.PromptStringWithValidator("Input File Path", func(ans interface{}) error {
|
||||
err := survey.Required(ans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(cast.ToString(ans))
|
||||
if err != nil {
|
||||
curPath, _ := os.Getwd()
|
||||
return fmt.Errorf("%w, (currenct path is [%s])", err, curPath)
|
||||
}
|
||||
return nil
|
||||
}, prompt.WithInputMessage("only support CSV"))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
package data_model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
)
|
||||
|
||||
// ListOptions is an options to List data-models.
|
||||
type ListOptions struct {
|
||||
WorkspaceName string
|
||||
Types []string
|
||||
Name string
|
||||
|
||||
workspaceClient factory.WorkspaceClient
|
||||
dataModelClient factory.DataModelClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewListOptions returns a reference to a ListOptions
|
||||
func NewListOptions(opt *clioptions.GlobalOptions) *ListOptions {
|
||||
return &ListOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdList(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewListOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "list data-models",
|
||||
Long: "list data-models of a specified workspace",
|
||||
Args: cobra.NoArgs,
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringVarP(&o.Name, "name", "n", o.Name, "data-model names to List")
|
||||
cmd.Flags().StringSliceVarP(&o.Types, "types", "t", o.Types, "data-model types to List")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *ListOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.dataModelClient, err = f.DataModelClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the List options
|
||||
func (o *ListOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
for _, t := range o.Types {
|
||||
if t != consts.DataModelTypeEntity && t != consts.DataModelTypeEntitySet && t != consts.DataModelTypeWorkspace {
|
||||
return fmt.Errorf("data-model type %s not support", t)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the List data-model command
|
||||
func (o *ListOptions) Run(args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.Name != "" {
|
||||
dataModelID, err := ConvertDataModelNameIntoID(ctx, o.dataModelClient, workspaceID, o.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers, rows, err := cmd.ParseWholeDataModel(ctx, o.dataModelClient, workspaceID, dataModelID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rows) > 10000 {
|
||||
o.formatter.Write(fmt.Sprintf("It will take a while to collect in that the length of dataModel is too big: %d rows", len(rows)))
|
||||
}
|
||||
o.formatter.Write(getDataModelStruct(headers, rows))
|
||||
return nil
|
||||
}
|
||||
|
||||
req := &convert.ListDataModelsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
}
|
||||
if len(o.Types) > 0 {
|
||||
req.Types = o.Types
|
||||
}
|
||||
resp, err := o.dataModelClient.ListDataModels(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.formatter.Write(resp.Items)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetPromptArgs() ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name, err = prompt.PromptOptionalString("DataModel Name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.Name == "" {
|
||||
o.Types, err = prompt.PromptStringMultiSelect("DataModel Types", 3,
|
||||
[]string{consts.DataModelTypeEntity, consts.DataModelTypeEntitySet, consts.DataModelTypeWorkspace},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TableFormat
|
||||
}
|
||||
|
||||
func ConvertDataModelNameIntoID(ctx context.Context, dataModelClient factory.DataModelClient, wsID, dataModelName string) (string, error) {
|
||||
resp, err := dataModelClient.ListDataModels(ctx, &convert.ListDataModelsRequest{
|
||||
WorkspaceID: wsID,
|
||||
SearchWord: dataModelName,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, item := range resp.Items {
|
||||
if item.Name == dataModelName {
|
||||
return item.ID, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no data-model named %s found", dataModelName)
|
||||
}
|
||||
|
||||
func getDataModelStruct(headers []string, rows [][]string) interface{} {
|
||||
f := make([]reflect.StructField, len(headers))
|
||||
fields := headers
|
||||
tOfStr := reflect.TypeOf("")
|
||||
|
||||
for i, v := range fields {
|
||||
f[i] = reflect.StructField{
|
||||
Name: strcase.ToCamel(reflect.ValueOf(v).Interface().(string)),
|
||||
Type: tOfStr,
|
||||
}
|
||||
}
|
||||
|
||||
t := reflect.StructOf(f)
|
||||
items := reflect.MakeSlice(reflect.SliceOf(t), len(rows), len(rows))
|
||||
|
||||
for i, row := range rows {
|
||||
e := reflect.New(t).Elem()
|
||||
for j, elem := range row {
|
||||
e.Field(j).Set(reflect.ValueOf(elem))
|
||||
}
|
||||
items.Index(i).Set(e)
|
||||
}
|
||||
|
||||
return items.Interface()
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
func GetAllRuns(ctx context.Context, submissionClient factory.SubmissionClient, workspaceID string, submissionID string) ([]convert.RunItem, error) {
|
||||
resp, err := submissionClient.ListRuns(ctx, &convert.ListRunsRequest{
|
||||
Page: 1,
|
||||
Size: 100,
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentNum := resp.Size
|
||||
currentPage := resp.Page
|
||||
|
||||
items := resp.Items
|
||||
|
||||
for currentNum < resp.Total {
|
||||
currentNum += len(resp.Items)
|
||||
currentPage += 1
|
||||
|
||||
resp, err = submissionClient.ListRuns(ctx, &convert.ListRunsRequest{
|
||||
Page: currentPage,
|
||||
Size: 100,
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, resp.Items...)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func ParseWholeDataModel(ctx context.Context, dataModelClient factory.DataModelClient, workspaceID, dataModelID string) ([]string, [][]string, error) {
|
||||
resp, err := dataModelClient.ListDataModelRows(ctx, &convert.ListDataModelRowsRequest{
|
||||
Page: 1,
|
||||
Size: 100,
|
||||
WorkspaceID: workspaceID,
|
||||
ID: dataModelID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
currentNum := int64(resp.Size)
|
||||
currentPage := resp.Page
|
||||
|
||||
headers := resp.Headers
|
||||
rows := resp.Rows
|
||||
|
||||
for currentNum < resp.Total {
|
||||
currentNum += int64(resp.Size)
|
||||
currentPage += 1
|
||||
|
||||
resp, err := dataModelClient.ListDataModelRows(ctx, &convert.ListDataModelRowsRequest{
|
||||
Page: currentPage,
|
||||
Size: 100,
|
||||
WorkspaceID: workspaceID,
|
||||
ID: dataModelID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rows = append(rows, resp.Rows...)
|
||||
}
|
||||
return headers, rows, nil
|
||||
}
|
||||
|
||||
func GetWorkspaceName(timeout int, workspaceClient factory.WorkspaceClient) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
|
||||
defer cancel()
|
||||
|
||||
resp, err := workspaceClient.ListWorkspaces(ctx, &convert.ListWorkspacesRequest{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.Total == 0 {
|
||||
return "", fmt.Errorf("no workspace found")
|
||||
}
|
||||
|
||||
var workspaceName string
|
||||
if resp.Total > 20 {
|
||||
workspaceName, err = prompt.PromptRequiredString("WorkspaceName", prompt.WithInputMessage(""))
|
||||
} else {
|
||||
items := make([]string, len(resp.Items))
|
||||
for j := range resp.Items {
|
||||
workspace := resp.Items[j]
|
||||
items[j] = fmt.Sprintf("%s", workspace.Name)
|
||||
}
|
||||
workspaceName, err = prompt.PromptStringSelect("WorkspaceName", 10, items)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return workspaceName, nil
|
||||
}
|
||||
|
||||
var ExampleTemplate = template.Must(template.New("Example").Parse(
|
||||
` # Use Command-Line mode:
|
||||
"bioctl {{.Use}} {{- range .OptionsUsage }} {{ . }} {{- end }}"
|
||||
|
||||
# Enter Interactive mode:
|
||||
1. Firstly input parent Command:
|
||||
"bioctl {{.ParentName}}"
|
||||
|
||||
2. Select Subcommand
|
||||
"{{.Name}}"
|
||||
`))
|
||||
|
||||
type ExampleData struct {
|
||||
ParentName string
|
||||
Name string
|
||||
Use string
|
||||
OptionsUsage []string
|
||||
}
|
||||
|
||||
// "# list workspaces: bioctl workspace list"
|
||||
func GenExample(cmd *cobra.Command) string {
|
||||
// template.
|
||||
flags := cmd.Flags()
|
||||
// cmd.Args
|
||||
flags.FlagUsages()
|
||||
data := ExampleData{
|
||||
ParentName: cmd.Parent().Name(),
|
||||
Name: cmd.Name(),
|
||||
Use: cmd.Use,
|
||||
}
|
||||
flags.VisitAll(func(flag *pflag.Flag) {
|
||||
var option string
|
||||
if flag.Shorthand != "" {
|
||||
option += fmt.Sprintf("-%s/", flag.Shorthand)
|
||||
}
|
||||
option += fmt.Sprintf("--%s [%s]", flag.Name, flag.Value.Type())
|
||||
data.OptionsUsage = append(data.OptionsUsage, option)
|
||||
})
|
||||
|
||||
var res bytes.Buffer
|
||||
ExampleTemplate.Execute(&res, data)
|
||||
|
||||
return res.String()
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package submission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// DeleteOptions is an options to delete a workspace.
|
||||
type DeleteOptions struct {
|
||||
WorkspaceName string
|
||||
|
||||
submissionClient factory.SubmissionClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewDeleteOptions returns a reference to a DeleteOptions
|
||||
func NewDeleteOptions(opt *clioptions.GlobalOptions) *DeleteOptions {
|
||||
return &DeleteOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdDelete(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewDeleteOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete <submission_id>",
|
||||
Short: "delete the submission",
|
||||
Long: "delete the submission",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *DeleteOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.submissionClient, err = f.SubmissionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the delete options
|
||||
func (o *DeleteOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the delete workspace command
|
||||
func (o *DeleteOptions) Run(args []string) error {
|
||||
submissionID := args[0]
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = o.submissionClient.DeleteSubmission(ctx, &convert.DeleteSubmissionRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
ID: submissionID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.formatter.Write(fmt.Sprintf("submission [%s] will be deleted soon", submissionID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetPromptArgs() ([]string, error) {
|
||||
submissionID, err := prompt.PromptRequiredString("Submission ID")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return []string{submissionID}, nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
package submission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/internal/context/submission/application/query/submission"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
)
|
||||
|
||||
// ListOptions is an options to List submissions.
|
||||
type ListOptions struct {
|
||||
WorkspaceName string
|
||||
Status []string
|
||||
Page int32
|
||||
Size int32
|
||||
OrderBy string
|
||||
SearchWords []string
|
||||
Ids []string
|
||||
|
||||
workspaceClient factory.WorkspaceClient
|
||||
submissionClient factory.SubmissionClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewListOptions returns a reference to a ListOptions
|
||||
func NewListOptions(opt *clioptions.GlobalOptions) *ListOptions {
|
||||
return &ListOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdList(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewListOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "list submissions",
|
||||
Long: "list submissions of a specified workspace",
|
||||
Args: cobra.NoArgs,
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringSliceVar(&o.Status, "status", o.Status, "filtered status")
|
||||
cmd.Flags().Int32VarP(&o.Page, "page", "p", 1, "The page number")
|
||||
cmd.Flags().Int32VarP(&o.Size, "size", "s", 10, "The page size")
|
||||
cmd.Flags().StringVar(&o.OrderBy, "order-by", o.OrderBy, "The order-by field")
|
||||
cmd.Flags().StringSliceVar(&o.SearchWords, "search-word", o.SearchWords, "The search word")
|
||||
cmd.Flags().StringSliceVar(&o.Ids, "ids", o.Ids, "The ids of the workspace.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *ListOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.submissionClient, err = f.SubmissionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
if len(o.Ids) > 0 {
|
||||
o.options.Stream.OutputFormat = formatter.JsonFormat
|
||||
} else {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the List options
|
||||
func (o *ListOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the List submission command
|
||||
func (o *ListOptions) Run(args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &convert.ListSubmissionsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
Page: int(o.Page),
|
||||
Size: int(o.Size),
|
||||
}
|
||||
|
||||
if o.OrderBy != "" {
|
||||
req.OrderBy = o.OrderBy
|
||||
}
|
||||
if len(o.SearchWords) > 0 {
|
||||
req.SearchWord = strings.Join(o.SearchWords, consts.QuerySliceDelimiter)
|
||||
}
|
||||
if len(o.Status) > 0 {
|
||||
req.Status = o.Status
|
||||
}
|
||||
if len(o.Ids) > 0 {
|
||||
req.IDs = o.Ids
|
||||
}
|
||||
resp, err := o.submissionClient.ListSubmissions(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter.Write(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetPromptArgs() ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Page, err = prompt.PromptRequiredInt32("Page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Size, err = prompt.PromptRequiredInt32("Size")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orderByFields, err := prompt.PromptStringMultiSelect("OrderBy", 2, []string{submission.OrderByName, submission.OrderByStartTime})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(orderByFields) > 0 {
|
||||
for i, field := range orderByFields {
|
||||
ascending, err := prompt.PromptStringSelect(fmt.Sprintf("%s Ascending", field), 2, []string{consts.ASCOrdering, consts.DESCOrdering})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderByFields[i] += consts.OrderDelimiter + ascending
|
||||
}
|
||||
o.OrderBy = strings.Join(orderByFields, ",")
|
||||
}
|
||||
|
||||
o.Status, err = prompt.PromptStringMultiSelect("Status", 7, []string{consts.SubmissionPending,
|
||||
consts.SubmissionRunning, consts.SubmissionFailed, consts.SubmissionFinished, consts.SubmissionSucceeded, consts.SubmissionCancelling,
|
||||
consts.SubmissionCancelled})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.SearchWords, err = prompt.PromptStringSlice("SearchWords")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Ids, err = prompt.PromptStringSlice("IDs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// correct formatter
|
||||
if len(o.Ids) > 0 {
|
||||
o.options.Stream.OutputFormat = formatter.JsonFormat
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TableFormat
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package submission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// LogOptions is an options to log a workspace.
|
||||
type LogOptions struct {
|
||||
WorkspaceName string
|
||||
RunID string
|
||||
TaskName string
|
||||
|
||||
submissionClient factory.SubmissionClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewLogOptions returns a reference to a LogOptions
|
||||
func NewLogOptions(opt *clioptions.GlobalOptions) *LogOptions {
|
||||
return &LogOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdLog(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewLogOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "log <submission_id>",
|
||||
Short: "get the log of the submission",
|
||||
Long: "get the log of the submission",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringVarP(&o.RunID, "run-id", "r", o.RunID, "The RunID of the submission.")
|
||||
cmd.Flags().StringVarP(&o.TaskName, "task-name", "t", o.TaskName, "The TaskName of the submission")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *LogOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.submissionClient, err = f.SubmissionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the log options
|
||||
func (o *LogOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.TaskName != "" {
|
||||
if o.RunID == "" {
|
||||
return fmt.Errorf("must specify a run id before specifying a task name ")
|
||||
}
|
||||
}
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the log workspace command
|
||||
func (o *LogOptions) Run(args []string) error {
|
||||
submissionID := args[0]
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.RunID != "" {
|
||||
runResp, err := o.submissionClient.ListRuns(ctx, &convert.ListRunsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
IDs: []string{o.RunID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(runResp.Items) == 0 {
|
||||
return fmt.Errorf("run: %s not found", o.RunID)
|
||||
}
|
||||
|
||||
if o.TaskName != "" {
|
||||
taskResp, err := o.submissionClient.ListTasks(ctx, &convert.ListTasksRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
RunID: o.RunID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range taskResp.Items {
|
||||
if item.Name == o.TaskName {
|
||||
if item.Stdout != "" {
|
||||
o.formatter.Write(item.Stdout)
|
||||
} else {
|
||||
o.formatter.Write(fmt.Sprintf("the Stdout of task [%s] is unavailable now", o.TaskName))
|
||||
}
|
||||
if item.Stderr != "" {
|
||||
o.formatter.Write(item.Stderr)
|
||||
} else {
|
||||
o.formatter.Write(fmt.Sprintf("the Stderr of task [%s] is unavailable now", o.TaskName))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(taskResp.Items) == 0 {
|
||||
return fmt.Errorf("task: %s not found", o.TaskName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if runResp.Items[0].Log != nil {
|
||||
o.formatter.Write(runResp.Items[0].Log)
|
||||
} else {
|
||||
o.formatter.Write(fmt.Sprintf("the log of run [%s] is unavailable now", runResp.Items[0].ID))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
runs, err := cmd.GetAllRuns(ctx, o.submissionClient, workspaceID, submissionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, run := range runs {
|
||||
if run.Log != nil {
|
||||
o.formatter.Write(struct {
|
||||
Log *string
|
||||
RunID string
|
||||
}{
|
||||
Log: run.Log,
|
||||
RunID: run.ID,
|
||||
})
|
||||
continue
|
||||
}
|
||||
o.formatter.Write(fmt.Sprintf("the log of run [%s] is unavailable now", run.ID))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LogOptions) GetPromptArgs() ([]string, error) {
|
||||
submissionID, err := prompt.PromptRequiredString("Submission ID")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return []string{submissionID}, nil
|
||||
}
|
||||
|
||||
func (o *LogOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.RunID, err = prompt.PromptOptionalString("Run ID")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.RunID != "" {
|
||||
o.TaskName, err = prompt.PromptOptionalString("Task Name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LogOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
package submission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// OutputOptions is an options to output a workspace.
|
||||
type OutputOptions struct {
|
||||
WorkspaceName string
|
||||
RunID string
|
||||
TaskName string
|
||||
|
||||
submissionClient factory.SubmissionClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewOutputOptions returns a reference to a OutputOptions
|
||||
func NewOutputOptions(opt *clioptions.GlobalOptions) *OutputOptions {
|
||||
return &OutputOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdOutput(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewOutputOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "output <submission_id>",
|
||||
Short: "get the output of the submission",
|
||||
Long: "get the output of the submission",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringVarP(&o.RunID, "run-id", "r", o.RunID, "The RunID of the submission.")
|
||||
cmd.Flags().StringVarP(&o.TaskName, "task-name", "t", o.TaskName, "The TaskName of the submission")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *OutputOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.submissionClient, err = f.SubmissionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the output options
|
||||
func (o *OutputOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the output workspace command
|
||||
func (o *OutputOptions) Run(args []string) error {
|
||||
submissionID := args[0]
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.RunID != "" {
|
||||
runResp, err := o.submissionClient.ListRuns(ctx, &convert.ListRunsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
IDs: []string{o.RunID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(runResp.Items) == 0 {
|
||||
return fmt.Errorf("run: %s not found", o.TaskName)
|
||||
}
|
||||
|
||||
if o.TaskName != "" {
|
||||
taskResp, err := o.submissionClient.ListTasks(ctx, &convert.ListTasksRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
RunID: o.RunID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range taskResp.Items {
|
||||
if item.Name == o.TaskName {
|
||||
if item.Stdout != "" {
|
||||
o.formatter.Write(item.Stdout)
|
||||
} else {
|
||||
o.formatter.Write(fmt.Sprintf("the outputs of task [%s] is unavailable now", o.TaskName))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(taskResp.Items) == 0 {
|
||||
return fmt.Errorf("task: %s not found", o.TaskName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if runResp.Items[0].Outputs != "" {
|
||||
o.formatter.Write(runResp.Items[0].Outputs)
|
||||
} else {
|
||||
o.formatter.Write(fmt.Sprintf("the outputs of run [%s] is unavailable now", runResp.Items[0].ID))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
runs, err := cmd.GetAllRuns(ctx, o.submissionClient, workspaceID, submissionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, run := range runs {
|
||||
if run.Log != nil {
|
||||
o.formatter.Write(struct {
|
||||
RunID string
|
||||
Outputs string
|
||||
}{
|
||||
RunID: run.ID,
|
||||
Outputs: run.Outputs,
|
||||
})
|
||||
continue
|
||||
}
|
||||
o.formatter.Write(fmt.Sprintf("the outputs of run [%s] is unavailable now", run.ID))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OutputOptions) GetPromptArgs() ([]string, error) {
|
||||
submissionID, err := prompt.PromptRequiredString("Submission ID")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return []string{submissionID}, nil
|
||||
}
|
||||
|
||||
func (o *OutputOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.RunID, err = prompt.PromptOptionalString("Run ID")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.RunID != "" {
|
||||
o.TaskName, err = prompt.PromptOptionalString("Task Name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OutputOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
package submission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// QueryOptions is an options to query a workspace.
|
||||
type QueryOptions struct {
|
||||
WorkspaceName string
|
||||
RunID string
|
||||
TaskName string
|
||||
|
||||
submissionClient factory.SubmissionClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewQueryOptions returns a reference to a QueryOptions
|
||||
func NewQueryOptions(opt *clioptions.GlobalOptions) *QueryOptions {
|
||||
return &QueryOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdQuery(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewQueryOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "query <submission_id>",
|
||||
Short: "get the status of the submission",
|
||||
Long: "get the status of the submission",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringVarP(&o.RunID, "run-id", "r", o.RunID, "The RunID of the submission.")
|
||||
cmd.Flags().StringVarP(&o.TaskName, "task-name", "t", o.TaskName, "The TaskName of the submission")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *QueryOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.submissionClient, err = f.SubmissionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the query options
|
||||
func (o *QueryOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.TaskName != "" {
|
||||
if o.RunID == "" {
|
||||
return fmt.Errorf("must specify a run id before specifying a task name ")
|
||||
}
|
||||
}
|
||||
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the query workspace command
|
||||
func (o *QueryOptions) Run(args []string) error {
|
||||
submissionID := args[0]
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := o.submissionClient.ListSubmissions(ctx, &convert.ListSubmissionsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
IDs: []string{submissionID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(resp.Items) == 0 {
|
||||
return fmt.Errorf("submission: %s not found", submissionID)
|
||||
}
|
||||
|
||||
if o.RunID != "" {
|
||||
runResp, err := o.submissionClient.ListRuns(ctx, &convert.ListRunsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
IDs: []string{o.RunID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(runResp.Items) == 0 {
|
||||
return fmt.Errorf("run: %s not found", o.TaskName)
|
||||
}
|
||||
|
||||
if o.TaskName != "" {
|
||||
taskResp, err := o.submissionClient.ListTasks(ctx, &convert.ListTasksRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
RunID: o.RunID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range taskResp.Items {
|
||||
if item.Name == o.TaskName {
|
||||
o.formatter.Write(item.Status)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("task: %s not found", o.TaskName)
|
||||
}
|
||||
|
||||
o.formatter.Write(runResp.Items[0].Status)
|
||||
return nil
|
||||
}
|
||||
|
||||
o.formatter.Write(resp.Items[0].Status)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *QueryOptions) GetPromptArgs() ([]string, error) {
|
||||
submissionID, err := prompt.PromptRequiredString("Submission ID")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return []string{submissionID}, nil
|
||||
}
|
||||
|
||||
func (o *QueryOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.RunID, err = prompt.PromptOptionalString("Run ID")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.RunID != "" {
|
||||
o.TaskName, err = prompt.PromptOptionalString("Task Name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *QueryOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package submission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// StopOptions is an options to stop a workspace.
|
||||
type StopOptions struct {
|
||||
WorkspaceName string
|
||||
RunID string
|
||||
|
||||
submissionClient factory.SubmissionClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewStopOptions returns a reference to a StopOptions
|
||||
func NewStopOptions(opt *clioptions.GlobalOptions) *StopOptions {
|
||||
return &StopOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdStop(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewStopOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "stop <submission_id>",
|
||||
Short: "stop the submission",
|
||||
Long: "stop the submission",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringVarP(&o.RunID, "run-id", "r", o.RunID, "The RunID of the submission.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *StopOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.submissionClient, err = f.SubmissionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the stop options
|
||||
func (o *StopOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the stop workspace command
|
||||
func (o *StopOptions) Run(args []string) error {
|
||||
submissionID := args[0]
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.RunID != "" {
|
||||
_, err := o.submissionClient.CancelRun(ctx, &convert.CancelRunRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
SubmissionID: submissionID,
|
||||
ID: o.RunID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.formatter.Write(fmt.Sprintf("run [%s] will be canceled soon", o.RunID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = o.submissionClient.CancelSubmission(ctx, &convert.CancelSubmissionRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
ID: submissionID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.formatter.Write(fmt.Sprintf("submission [%s] will be canceled soon", submissionID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *StopOptions) GetPromptArgs() ([]string, error) {
|
||||
submissionID, err := prompt.PromptRequiredString("Submission ID")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return []string{submissionID}, nil
|
||||
}
|
||||
|
||||
func (o *StopOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.RunID, err = prompt.PromptOptionalString("Run ID")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *StopOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package submission
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
func NewCmdSubmission(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "submission",
|
||||
Short: "submission command",
|
||||
Long: `submission command`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: prompt.SelectSubCommand,
|
||||
}
|
||||
cmd.AddCommand(NewCmdSubmit(opt))
|
||||
cmd.AddCommand(NewCmdQuery(opt))
|
||||
cmd.AddCommand(NewCmdDelete(opt))
|
||||
cmd.AddCommand(NewCmdStop(opt))
|
||||
cmd.AddCommand(NewCmdLog(opt))
|
||||
cmd.AddCommand(NewCmdList(opt))
|
||||
cmd.AddCommand(NewCmdOutput(opt))
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
package submission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
clidatamodel "github.com/Bio-OS/bioos/internal/bioctl/cmd/data-model"
|
||||
cliworkflow "github.com/Bio-OS/bioos/internal/bioctl/cmd/workflow"
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
)
|
||||
|
||||
type InputsOutputs struct {
|
||||
InputsTemplate map[string]interface{}
|
||||
OutputsTemplate map[string]interface{}
|
||||
InputsMaterial map[string]interface{}
|
||||
OutputsMaterial map[string]interface{}
|
||||
}
|
||||
|
||||
const emptyJsonStr = "null"
|
||||
|
||||
// SubmitOptions is an options to submit a workspace.
|
||||
type SubmitOptions struct {
|
||||
WorkspaceName string
|
||||
Description string
|
||||
Type string
|
||||
DataModelName string
|
||||
DataModelRowIDs []string
|
||||
File string
|
||||
ReadFromCache bool
|
||||
|
||||
InputsTemplate string
|
||||
OutputsTemplate string
|
||||
InputsMaterial string
|
||||
OutputsMaterial string
|
||||
|
||||
dataModelClient factory.DataModelClient
|
||||
workflowClient factory.WorkflowClient
|
||||
submissionClient factory.SubmissionClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewSubmitOptions returns a reference to a SubmitOptions
|
||||
func NewSubmitOptions(opt *clioptions.GlobalOptions) *SubmitOptions {
|
||||
return &SubmitOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdSubmit(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewSubmitOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "submit <workflow_name>",
|
||||
Short: "submit a workflow",
|
||||
Long: "submit a workflow",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The workspace name")
|
||||
cmd.Flags().StringVarP(&o.Description, "description", "d", o.Description, "The description of the submission.")
|
||||
cmd.Flags().StringVarP(&o.Type, "type", "t", o.Type, "The Type of the submission.")
|
||||
cmd.Flags().StringVarP(&o.DataModelName, "data-model", "m", o.DataModelName, "The name of the data-model this submission will use.")
|
||||
cmd.Flags().StringSliceVar(&o.DataModelRowIDs, "data-model-rows", o.DataModelRowIDs, "The rows of the data-model this submission will use.")
|
||||
cmd.Flags().StringVarP(&o.File, "file", "f", o.File, "The file path of Inputs/Outputs.")
|
||||
cmd.Flags().BoolVar(&o.ReadFromCache, "call-caching", true, "use previous cache of the submission or not.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *SubmitOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.submissionClient, err = f.SubmissionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.dataModelClient, err = f.DataModelClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.workflowClient, err = f.WorkflowClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the submit options
|
||||
func (o *SubmitOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("need to specify a workspace name")
|
||||
}
|
||||
|
||||
if o.Type == "" {
|
||||
return fmt.Errorf("submission type cannot be empty")
|
||||
}
|
||||
|
||||
if o.File == "" {
|
||||
return fmt.Errorf("need to specify a file to declare inputs and outputs")
|
||||
}
|
||||
|
||||
err := o.parseInputsAndOutputsFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.Type == consts.DataModelTypeSubmission {
|
||||
if o.InputsTemplate == "" {
|
||||
return fmt.Errorf("InputsTemplate cannot be empty")
|
||||
}
|
||||
if o.DataModelName == "" {
|
||||
return fmt.Errorf("need to specify a data-model")
|
||||
}
|
||||
} else if o.Type == consts.FilePathTypeSubmission {
|
||||
if o.InputsMaterial == "" {
|
||||
return fmt.Errorf("InputsMaterial cannot be empty")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("submission type %s not support", o.Type)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the submit workspace command
|
||||
func (o *SubmitOptions) Run(args []string) error {
|
||||
workflowName := args[0]
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workflowID, err := cliworkflow.ConvertWorkflowNameIntoID(ctx, o.workflowClient, workspaceID, workflowName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &convert.CreateSubmissionRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
Name: fmt.Sprintf("%s-history-%s", workflowName, time.Now().Format("2006-01-02-15-04-05")),
|
||||
WorkflowID: workflowID,
|
||||
Type: o.Type,
|
||||
ExposedOptions: convert.ExposedOptions{
|
||||
ReadFromCache: o.ReadFromCache,
|
||||
},
|
||||
}
|
||||
|
||||
if o.Type == consts.DataModelTypeSubmission {
|
||||
dataModelID, err := clidatamodel.ConvertDataModelNameIntoID(ctx, o.dataModelClient, workspaceID, o.DataModelName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(o.DataModelRowIDs) == 0 {
|
||||
idsResp, err := o.dataModelClient.ListAllDataModelRowIDs(ctx, &convert.ListAllDataModelRowIDsRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
ID: dataModelID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.DataModelRowIDs = idsResp.RowIDs
|
||||
}
|
||||
req.Entity = &convert.Entity{
|
||||
DataModelID: dataModelID,
|
||||
DataModelRowIDs: o.DataModelRowIDs,
|
||||
InputsTemplate: o.InputsTemplate,
|
||||
OutputsTemplate: o.OutputsTemplate,
|
||||
}
|
||||
} else if o.Type == consts.FilePathTypeSubmission {
|
||||
req.InOutMaterial = &convert.InOutMaterial{
|
||||
InputsMaterial: o.InputsMaterial,
|
||||
OutputsMaterial: o.OutputsMaterial,
|
||||
}
|
||||
}
|
||||
|
||||
if o.Description != "" {
|
||||
req.Description = &o.Description
|
||||
}
|
||||
|
||||
resp, err := o.submissionClient.CreateSubmission(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter.Write(resp.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *SubmitOptions) GetPromptArgs() ([]string, error) {
|
||||
workflowName, err := prompt.PromptRequiredString("Workflow Name")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
return []string{workflowName}, nil
|
||||
}
|
||||
|
||||
func (o *SubmitOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
// TODO put it in GetPromptArgs? enhance interactive user experience
|
||||
o.WorkspaceName, err = cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Description, err = prompt.PromptOptionalString("Description")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Type, err = prompt.PromptStringSelect("Submission Type", 2, []string{consts.DataModelTypeSubmission, consts.FilePathTypeSubmission})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.Type == consts.DataModelTypeSubmission {
|
||||
o.DataModelName, err = prompt.PromptRequiredString("DataModel Name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DataModelRowIDs, err = prompt.PromptStringSlice(fmt.Sprintf("RowIDs of [%s]", o.DataModelName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
o.File, err = prompt.PromptStringWithValidator("Inputs and Outputs FilePath", func(ans interface{}) error {
|
||||
err := survey.Required(ans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(cast.ToString(ans))
|
||||
if err != nil {
|
||||
curPath, _ := os.Getwd()
|
||||
return fmt.Errorf("%w, (currenct path is [%s])", err, curPath)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.ReadFromCache, err = prompt.PromptBoolSelect("ReadFromCache")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *SubmitOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
||||
|
||||
func (o *SubmitOptions) parseInputsAndOutputsFile() error {
|
||||
bytes, err := os.ReadFile(o.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inOuts := InputsOutputs{}
|
||||
err = json.Unmarshal(bytes, &inOuts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataInputsTemplate, _ := json.Marshal(inOuts.InputsTemplate)
|
||||
o.InputsTemplate = string(dataInputsTemplate)
|
||||
dataOutputsTemplate, _ := json.Marshal(inOuts.OutputsTemplate)
|
||||
o.OutputsTemplate = string(dataOutputsTemplate)
|
||||
dataInputsMaterial, _ := json.Marshal(inOuts.InputsMaterial)
|
||||
o.InputsMaterial = string(dataInputsMaterial)
|
||||
dataOutputsMaterial, _ := json.Marshal(inOuts.OutputsMaterial)
|
||||
o.OutputsMaterial = string(dataOutputsMaterial)
|
||||
|
||||
if o.InputsTemplate == emptyJsonStr {
|
||||
o.InputsTemplate = ""
|
||||
}
|
||||
if o.OutputsTemplate == emptyJsonStr {
|
||||
o.OutputsTemplate = ""
|
||||
}
|
||||
if o.InputsMaterial == emptyJsonStr {
|
||||
o.InputsMaterial = ""
|
||||
}
|
||||
if o.OutputsMaterial == emptyJsonStr {
|
||||
o.OutputsMaterial = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/pkg/version"
|
||||
)
|
||||
|
||||
type VersionOptions struct {
|
||||
formatter formatter.Formatter
|
||||
versionClient factory.VersionClient
|
||||
|
||||
option *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewVersionOptions returns a reference to a CreateOptions
|
||||
func NewVersionOptions(opt *clioptions.GlobalOptions) *VersionOptions {
|
||||
return &VersionOptions{
|
||||
option: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdVersion(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewVersionOptions(opt)
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "version command",
|
||||
Long: `version command`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
utils.CheckErr(o.Complete())
|
||||
utils.CheckErr(o.Validate())
|
||||
utils.CheckErr(o.Run(args))
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *VersionOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.option.Client)
|
||||
o.versionClient, err = f.VersionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.option.Stream.OutputFormat, o.option.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *VersionOptions) Validate() error {
|
||||
//return o.option.Validate()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *VersionOptions) Run(args []string) error {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(o.option.Client.Timeout)*time.Second)
|
||||
defer cancelFunc()
|
||||
|
||||
switch o.formatter.(type) {
|
||||
case *formatter.Json, *formatter.Yaml:
|
||||
m := map[string]interface{}{
|
||||
"clientVersion": version.Get(),
|
||||
}
|
||||
info, err := o.versionClient.Version(ctx)
|
||||
if err != nil {
|
||||
m["serverVersion"] = err.Error()
|
||||
} else {
|
||||
m["serverVersion"] = info
|
||||
}
|
||||
o.formatter.Write(m)
|
||||
|
||||
case *formatter.Table:
|
||||
// todo optimize here
|
||||
o.formatter.Write(version.Get())
|
||||
info, err := o.versionClient.Version(ctx)
|
||||
if err != nil {
|
||||
o.formatter.Write(err.Error())
|
||||
} else {
|
||||
o.formatter.Write(info)
|
||||
}
|
||||
|
||||
case *formatter.Text:
|
||||
o.formatter.Write("clientVersion")
|
||||
o.formatter.Write(version.Get())
|
||||
o.formatter.Write("serverVersion")
|
||||
info, err := o.versionClient.Version(ctx)
|
||||
if err != nil {
|
||||
o.formatter.Write(err.Error())
|
||||
} else {
|
||||
o.formatter.Write(info)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/internal/context/workspace/domain/workflow"
|
||||
)
|
||||
|
||||
// CreateOptions is an options to create a workflow.
|
||||
type CreateOptions struct {
|
||||
WorkspaceName string
|
||||
Name string
|
||||
Description string
|
||||
Language string
|
||||
Source string
|
||||
URL string
|
||||
Tag string
|
||||
Token string
|
||||
MainWorkflowPath string
|
||||
|
||||
workflowClient factory.WorkflowClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
func (o *CreateOptions) GetPromptArgs() ([]string, error) {
|
||||
workflowName, err := prompt.PromptRequiredString("Name")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return []string{workflowName}, nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = prompt.PromptRequiredString("Workspace", prompt.WithInputMessage("Name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Description, err = prompt.PromptOptionalString("Description")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Language, err = prompt.PromptStringSelect("Language", 10, []string{workflow.Language})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Source, err = prompt.PromptStringSelect("Source", 2, []string{workflow.WorkflowSourceGit, workflow.WorkflowSourceFile})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.URL, err = prompt.PromptRequiredString("URL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Tag, err = prompt.PromptRequiredString("Tag", prompt.WithInputMessage("tag or branch"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Token, err = prompt.PromptOptionalString("Token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.MainWorkflowPath, err = prompt.PromptRequiredString("MainWorkflowPath", prompt.WithInputMessage("relative path"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
||||
|
||||
// NewCreateOptions returns a reference to a CreateOptions
|
||||
func NewCreateOptions(opt *clioptions.GlobalOptions) *CreateOptions {
|
||||
return &CreateOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdCreate(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewCreateOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "create a workflow",
|
||||
Long: "create a workflow",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The name of the workspace.")
|
||||
cmd.Flags().StringVarP(&o.Description, "description", "d", o.Description, "The description of the workflow.")
|
||||
cmd.Flags().StringVarP(&o.Language, "language", "l", o.Language, "The language of the workflow.")
|
||||
cmd.Flags().StringVar(&o.Source, "source", o.Source, "The source of the workflow.")
|
||||
cmd.Flags().StringVar(&o.URL, "url", o.URL, "The url of the workflow.")
|
||||
cmd.Flags().StringVarP(&o.Tag, "tag", o.Tag, "t", "The tag or branch of the workflow.")
|
||||
cmd.Flags().StringVar(&o.Token, "token", o.Token, "The token to clone repo of the workflow.")
|
||||
cmd.Flags().StringVarP(&o.MainWorkflowPath, "main-workflow-path", "p", o.MainWorkflowPath, "The main path of the workflow.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *CreateOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workflowClient, err = f.WorkflowClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the create options
|
||||
func (o *CreateOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.Language != workflow.Language {
|
||||
return fmt.Errorf("unspport language: %s", o.Language)
|
||||
}
|
||||
if o.Source != workflow.WorkflowSourceGit && o.Source != workflow.WorkflowSourceFile {
|
||||
return fmt.Errorf("unspport source: %s", o.Source)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the create workflow command
|
||||
func (o *CreateOptions) Run(args []string) error {
|
||||
workflowName := args[0]
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := &convert.CreateWorkflowRequest{
|
||||
WorkspaceID: workspaceID,
|
||||
Name: workflowName,
|
||||
Description: o.Description,
|
||||
Language: o.Language,
|
||||
Source: o.Source,
|
||||
MainWorkflowPath: o.MainWorkflowPath,
|
||||
Url: o.URL,
|
||||
Tag: o.Tag,
|
||||
Token: o.Token,
|
||||
}
|
||||
|
||||
var resp *convert.CreateWorkflowResponse
|
||||
resp, err = o.workflowClient.CreateWorkflow(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter.Write(resp.ID)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// DeleteOptions is an options to delete a workflow.
|
||||
type DeleteOptions struct {
|
||||
WorkspaceName string
|
||||
|
||||
workflowClient factory.WorkflowClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetPromptArgs() ([]string, error) {
|
||||
workflowName, err := prompt.PromptRequiredString("Workflow", prompt.WithInputMessage("Name"))
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return []string{workflowName}, nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = prompt.PromptRequiredString("Workspace", prompt.WithInputMessage("Name"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
||||
|
||||
// NewDeleteOptions returns a reference to a DeleteOptions.
|
||||
func NewDeleteOptions(opt *clioptions.GlobalOptions) *DeleteOptions {
|
||||
return &DeleteOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdDelete new a delete workflow cmd.
|
||||
func NewCmdDelete(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewDeleteOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "delete a workflow",
|
||||
Long: "delete a workflow",
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The name of the workspace.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *DeleteOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workflowClient, err = f.WorkflowClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the delete options
|
||||
func (o *DeleteOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the create workflow command
|
||||
func (o *DeleteOptions) Run(args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) != 0 {
|
||||
workflowName := args[0]
|
||||
var workflowID string
|
||||
workflowID, err = ConvertWorkflowNameIntoID(ctx, o.workflowClient, workspaceID, workflowName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = o.workflowClient.DeleteWorkflow(ctx, &convert.DeleteWorkflowRequest{
|
||||
ID: workflowID,
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
} else {
|
||||
// delete all workflow in special workspace
|
||||
pageNum := 1
|
||||
pageSize := 100
|
||||
for {
|
||||
var workspaces *convert.ListWorkflowsResponse
|
||||
workspaces, err = o.workflowClient.ListWorkflow(ctx, &convert.ListWorkflowsRequest{
|
||||
Page: pageNum,
|
||||
Size: pageSize,
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range workspaces.Items {
|
||||
_, err = o.workflowClient.DeleteWorkflow(ctx, &convert.DeleteWorkflowRequest{
|
||||
ID: item.ID,
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
}
|
||||
if len(workspaces.Items) < pageSize {
|
||||
return nil
|
||||
}
|
||||
pageNum++
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// ImportOptions is an options to get a workflow.
|
||||
type ImportOptions struct {
|
||||
WorkspaceName string
|
||||
Filepath string
|
||||
|
||||
create *CreateOptions
|
||||
|
||||
workflowClient factory.WorkflowClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetPromptArgs() ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = prompt.PromptRequiredString("Workspace", prompt.WithInputMessage("Name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Filepath, err = prompt.PromptStringWithValidator("Input File Path", func(ans interface{}) error {
|
||||
err := survey.Required(ans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(cast.ToString(ans))
|
||||
if err != nil {
|
||||
curPath, _ := os.Getwd()
|
||||
return fmt.Errorf("%w, (currenct path is [%s])", err, curPath)
|
||||
}
|
||||
return nil
|
||||
}, prompt.WithInputMessage("abs path"))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
||||
|
||||
// NewImportOptions returns a reference to a ImportOptions.
|
||||
func NewImportOptions(opt *clioptions.GlobalOptions) *ImportOptions {
|
||||
return &ImportOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdImport new a get workflow cmd.
|
||||
func NewCmdImport(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewImportOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "import a workflow",
|
||||
Long: "import a workflow",
|
||||
Example: `# import a workflow
|
||||
# import a workflow with json format
|
||||
{"name":"test","description":"test","language":"WDL","source":"git","URL":"https://github.com/xueerli/wdl.git","tag":"main","token":"","mainWorkflowPath":"no.wdl"}
|
||||
`,
|
||||
Args: cobra.ExactArgs(0),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.Filepath, "file", "f", o.WorkspaceName, "The file of the git info.")
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The name of workspace")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *ImportOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workflowClient, err = f.WorkflowClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the get options
|
||||
func (o *ImportOptions) Validate() error {
|
||||
stat, err := os.Stat(o.Filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.ReadFile(stat.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := ImportTemplate{}
|
||||
err = json.Unmarshal(file, &template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.create = &CreateOptions{
|
||||
WorkspaceName: o.WorkspaceName,
|
||||
Name: template.Name,
|
||||
Description: template.Description,
|
||||
Language: template.Language,
|
||||
Source: template.Source,
|
||||
URL: template.URL,
|
||||
Tag: template.Tag,
|
||||
Token: template.Token,
|
||||
MainWorkflowPath: template.MainWorkflowPath,
|
||||
options: o.options,
|
||||
}
|
||||
if err = o.Complete(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = o.create.Complete(); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.create.Validate()
|
||||
}
|
||||
|
||||
type ImportTemplate struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Language string `json:"language"`
|
||||
Source string `json:"source"`
|
||||
URL string `json:"URL"`
|
||||
Tag string `json:"tag"`
|
||||
Token string `json:"token"`
|
||||
MainWorkflowPath string `json:"mainWorkflowPath"`
|
||||
}
|
||||
|
||||
// Run run the create workflow command
|
||||
func (o *ImportOptions) Run(args []string) error {
|
||||
return o.create.Run([]string{o.create.Name})
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/internal/context/workspace/application/query/workflow"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
)
|
||||
|
||||
// ListOptions is an options to list workflow.
|
||||
type ListOptions struct {
|
||||
WorkspaceName string
|
||||
Page int32
|
||||
Size int32
|
||||
OrderBy string
|
||||
SearchWords []string
|
||||
Ids []string
|
||||
|
||||
workflowClient factory.WorkflowClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetPromptArgs() ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = prompt.PromptRequiredString("Workspace", prompt.WithInputMessage("Name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.SearchWords, err = prompt.PromptStringSlice("SearchWords")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderByFields, err := prompt.PromptStringMultiSelect("OrderBy", 2, []string{workflow.OrderByName, workflow.OrderByCreateTime})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(orderByFields) > 0 {
|
||||
for i, field := range orderByFields {
|
||||
ascending, err := prompt.PromptStringSelect(fmt.Sprintf("%s Ascending", field), 2, []string{consts.ASCOrdering, consts.DESCOrdering})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderByFields[i] += consts.OrderDelimiter + ascending
|
||||
}
|
||||
o.OrderBy = strings.Join(orderByFields, ",")
|
||||
}
|
||||
|
||||
o.Page, err = prompt.PromptRequiredInt32("Page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Size, err = prompt.PromptRequiredInt32("Size")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Ids, err = prompt.PromptStringSlice("IDs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// correct formatter
|
||||
if len(o.Ids) > 0 {
|
||||
o.options.Stream.OutputFormat = formatter.JsonFormat
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TableFormat
|
||||
}
|
||||
|
||||
// NewListOptions returns a reference to a ListOptions.
|
||||
func NewListOptions(opt *clioptions.GlobalOptions) *ListOptions {
|
||||
return &ListOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdList new a list workflow cmd.
|
||||
func NewCmdList(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewListOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "list workflow",
|
||||
Long: "list workflow",
|
||||
Args: cobra.ExactArgs(0),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The name of the workspace.")
|
||||
cmd.Flags().Int32Var(&o.Page, "page", 1, "The page number")
|
||||
cmd.Flags().Int32Var(&o.Size, "size", 10, "The page size")
|
||||
cmd.Flags().StringVar(&o.OrderBy, "order-by", o.OrderBy, "The order-by field: Name:desc")
|
||||
cmd.Flags().StringSliceVar(&o.SearchWords, "search-words", o.SearchWords, "The search word")
|
||||
cmd.Flags().StringSliceVar(&o.Ids, "ids", o.Ids, "The ids of the workspace.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *ListOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workflowClient, err = f.WorkflowClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
if len(o.Ids) > 0 {
|
||||
o.options.Stream.OutputFormat = formatter.JsonFormat
|
||||
} else {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the list options
|
||||
func (o *ListOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.WorkspaceName == "" {
|
||||
return fmt.Errorf("workspace not provide")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the create workflow command
|
||||
func (o *ListOptions) Run(_ []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := &convert.ListWorkflowsRequest{
|
||||
Page: int(o.Page),
|
||||
Size: int(o.Size),
|
||||
WorkspaceID: workspaceID,
|
||||
}
|
||||
if o.OrderBy != "" {
|
||||
req.OrderBy = o.OrderBy
|
||||
}
|
||||
if len(o.SearchWords) > 0 {
|
||||
req.SearchWord = strings.Join(o.SearchWords, consts.QuerySliceDelimiter)
|
||||
}
|
||||
if len(o.Ids) > 0 {
|
||||
req.IDs = strings.Join(o.Ids, consts.QuerySliceDelimiter)
|
||||
}
|
||||
resp, err := o.workflowClient.ListWorkflow(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter.Write(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertWorkflowNameIntoID convert workflow name into id
|
||||
func ConvertWorkflowNameIntoID(ctx context.Context, workflowClient factory.WorkflowClient, wsID string, name string) (id string, err error) {
|
||||
pageNum := 1
|
||||
pageSize := 100
|
||||
for {
|
||||
var listWorkflowsResponse *convert.ListWorkflowsResponse
|
||||
listWorkflowsResponse, err = workflowClient.ListWorkflow(ctx, &convert.ListWorkflowsRequest{
|
||||
Page: pageNum,
|
||||
Size: pageSize,
|
||||
SearchWord: name,
|
||||
WorkspaceID: wsID,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, item := range listWorkflowsResponse.Items {
|
||||
if item.Name == name {
|
||||
id = item.ID
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(listWorkflowsResponse.Items) < pageSize {
|
||||
err = fmt.Errorf("workflow: %s not found", name)
|
||||
return
|
||||
}
|
||||
pageNum++
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cliworkspace "github.com/Bio-OS/bioos/internal/bioctl/cmd/workspace"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// UpdateOptions is an options to update workflow.
|
||||
type UpdateOptions struct {
|
||||
WorkspaceName string
|
||||
Name string
|
||||
Description string
|
||||
|
||||
workflowClient factory.WorkflowClient
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
func (o *UpdateOptions) GetPromptArgs() ([]string, error) {
|
||||
workflowName, err := prompt.PromptRequiredString("Workflow", prompt.WithInputMessage("Name"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []string{workflowName}, nil
|
||||
}
|
||||
|
||||
func (o *UpdateOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.WorkspaceName, err = prompt.PromptRequiredString("Workspace", prompt.WithInputMessage("Name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name, err = prompt.PromptRequiredString("Name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Description, err = prompt.PromptOptionalString("Description")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *UpdateOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
||||
|
||||
// NewUpdateOptions returns a reference to a UpdateOptions.
|
||||
func NewUpdateOptions(opt *clioptions.GlobalOptions) *UpdateOptions {
|
||||
return &UpdateOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdUpdate new a update workflow cmd.
|
||||
func NewCmdUpdate(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewUpdateOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <workflow_name>",
|
||||
Short: "update a workflow",
|
||||
Long: "update a workflow",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.WorkspaceName, "workspace", "w", o.WorkspaceName, "The name of the workspace.")
|
||||
cmd.Flags().StringVarP(&o.Name, "name", "n", o.Name, "The name of the workflow.")
|
||||
cmd.Flags().StringVarP(&o.Description, "description", "d", o.Description, "The description of the workflow.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *UpdateOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workflowClient, err = f.WorkflowClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the list options
|
||||
func (o *UpdateOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the update workflow command
|
||||
func (o *UpdateOptions) Run(args []string) error {
|
||||
workflowName := args[0]
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
|
||||
workspaceID, err := cliworkspace.ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, o.WorkspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workflowID, err := ConvertWorkflowNameIntoID(ctx, o.workflowClient, workspaceID, workflowName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &convert.UpdateWorkflowRequest{
|
||||
ID: workflowID,
|
||||
WorkspaceID: workspaceID,
|
||||
}
|
||||
if o.Name != "" {
|
||||
req.Name = o.Name
|
||||
}
|
||||
if o.Description != "" {
|
||||
req.Description = o.Description
|
||||
}
|
||||
// todo update api needs to add more fields
|
||||
_, err = o.workflowClient.UpdateWorkflow(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
func NewCmdWorkflow(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "workflow",
|
||||
Short: "workflow command",
|
||||
Long: `workflow command`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: prompt.SelectSubCommand,
|
||||
}
|
||||
cmd.AddCommand(NewCmdCreate(opt))
|
||||
cmd.AddCommand(NewCmdImport(opt))
|
||||
cmd.AddCommand(NewCmdList(opt))
|
||||
cmd.AddCommand(NewCmdUpdate(opt))
|
||||
cmd.AddCommand(NewCmdDelete(opt))
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// CreateOptions is an options to create a workspace.
|
||||
type CreateOptions struct {
|
||||
Description string
|
||||
MountType string
|
||||
MountPath string
|
||||
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewCreateOptions returns a reference to a CreateOptions
|
||||
func NewCreateOptions(opt *clioptions.GlobalOptions) *CreateOptions {
|
||||
return &CreateOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdCreate(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewCreateOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create <workspace_name>",
|
||||
Short: "create a workspace",
|
||||
Long: "create a workspace",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.Description, "description", "d", o.Description, "The description of the workspace.")
|
||||
cmd.Flags().StringVarP(&o.MountType, "mount-type", "t", o.MountType, "The mount type of the workspace Storage.")
|
||||
cmd.Flags().StringVarP(&o.MountPath, "mount-path", "p", o.MountPath, "The mount path of the workspace Storage.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *CreateOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the create options
|
||||
func (o *CreateOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.MountType != "nfs" {
|
||||
return fmt.Errorf("workspace storage [%s] not support", o.MountType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the create workspace command
|
||||
func (o *CreateOptions) Run(args []string) error {
|
||||
workspaceName := args[0]
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
req := &convert.CreateWorkspaceRequest{
|
||||
Name: workspaceName,
|
||||
Description: o.Description,
|
||||
}
|
||||
if o.MountPath != "" {
|
||||
req.Storage = &convert.WorkspaceStorage{
|
||||
NFS: &convert.NFSWorkspaceStorage{
|
||||
MountPath: o.MountPath,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := o.workspaceClient.CreateWorkspace(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter.Write(resp.Id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) GetPromptArgs() ([]string, error) {
|
||||
workspaceName, err := prompt.PromptRequiredString("Name")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return []string{workspaceName}, nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.Description, err = prompt.PromptRequiredString("Description")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.MountPath, err = prompt.PromptRequiredString("MountPath", prompt.WithInputMessage("abs path"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.MountType, err = prompt.PromptStringSelect("MountType", 1, []string{"nfs"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
)
|
||||
|
||||
// DeleteOptions is an options to delete a workspace.
|
||||
type DeleteOptions struct {
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewDeleteOptions returns a reference to a DeleteOptions.
|
||||
func NewDeleteOptions(opt *clioptions.GlobalOptions) *DeleteOptions {
|
||||
return &DeleteOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdDelete new a delete workspace cmd.
|
||||
func NewCmdDelete(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewDeleteOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete <workspace_name>",
|
||||
Short: "delete a workspace",
|
||||
Long: "delete a workspace",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *DeleteOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the delete options
|
||||
func (o *DeleteOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the create workspace command
|
||||
func (o *DeleteOptions) Run(args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
|
||||
workspaceID, err := ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = o.workspaceClient.DeleteWorkspace(ctx, &convert.DeleteWorkspaceRequest{
|
||||
Id: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetPromptArgs() ([]string, error) {
|
||||
name, err := cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []string{name}, nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetPromptOptions() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
applog "github.com/Bio-OS/bioos/pkg/log"
|
||||
"github.com/Bio-OS/bioos/pkg/schema"
|
||||
"github.com/Bio-OS/bioos/pkg/utils"
|
||||
)
|
||||
|
||||
type ImportOptions struct {
|
||||
YamlPath string
|
||||
MountType string
|
||||
MountPath string
|
||||
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewImportOptions returns a reference to a ImportOptions
|
||||
func NewImportOptions(opt *clioptions.GlobalOptions) *ImportOptions {
|
||||
return &ImportOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdImport(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewImportOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "import a workspace",
|
||||
Long: "import a workspace",
|
||||
Args: cobra.NoArgs,
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.YamlPath, "yaml", "y", o.YamlPath, "The path of the workspace yaml file")
|
||||
cmd.Flags().StringVarP(&o.MountType, "mount-type", "t", o.MountType, "The mount type of the workspace Storage.")
|
||||
cmd.Flags().StringVarP(&o.MountPath, "mount-path", "p", o.MountPath, "The mount path of the workspace Storage.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *ImportOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the import options
|
||||
func (o *ImportOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.YamlPath == "" || path.Ext(o.YamlPath) != ".yaml" {
|
||||
return fmt.Errorf("you must choose a csv file to import")
|
||||
}
|
||||
_, err := os.Stat(o.YamlPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.MountType != "nfs" {
|
||||
return fmt.Errorf("workspace storage [%s] not support", o.MountType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the import workspace command
|
||||
func (o *ImportOptions) Run(args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
|
||||
bytes, err := os.ReadFile(o.YamlPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schema := &schema.WorkspaceTypedSchema{}
|
||||
err = yaml.Unmarshal(bytes, &schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zipFilePath, err := o.zipFiles(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(zipFilePath)
|
||||
|
||||
req := &convert.ImportWorkspaceRequest{
|
||||
FilePath: zipFilePath,
|
||||
MountType: o.MountType,
|
||||
}
|
||||
if o.MountPath != "" {
|
||||
req.MountPath = o.MountPath
|
||||
}
|
||||
|
||||
resp, err := o.workspaceClient.ImportWorkspace(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter.Write(resp.Id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetPromptArgs() ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
|
||||
o.YamlPath, err = prompt.PromptStringWithValidator("Workspace YamlPath File Path", func(ans interface{}) error {
|
||||
err = survey.Required(ans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(cast.ToString(ans))
|
||||
if err != nil {
|
||||
curPath, _ := os.Getwd()
|
||||
return fmt.Errorf("%w, (currenct path is [%s])", err, curPath)
|
||||
}
|
||||
return nil
|
||||
}, prompt.WithInputMessage("only support YamlPath"))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.MountPath, err = prompt.PromptRequiredString("MountPath", prompt.WithInputMessage("abs path"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.MountType, err = prompt.PromptStringSelect("MountType", 1, []string{"nfs"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ImportOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
||||
|
||||
func (o *ImportOptions) zipFiles(schema *schema.WorkspaceTypedSchema) (string, error) {
|
||||
yamlBase := path.Base(o.YamlPath)
|
||||
yamlFilePrefix := strings.TrimSuffix(yamlBase, path.Ext(o.YamlPath))
|
||||
dir := fmt.Sprintf("import-%s-%s-%s", yamlFilePrefix, o.MountType, rand.String(10))
|
||||
os.RemoveAll(dir)
|
||||
|
||||
err := os.Mkdir(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
applog.Errorf("fail to make tmp dir: %s", err)
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
baseDir := filepath.Dir(o.YamlPath)
|
||||
// copy all files to temp dir
|
||||
filesMap := map[string]string{o.YamlPath: path.Join(dir, consts.WorkspaceYAMLName)}
|
||||
for _, dataModel := range schema.DataModels {
|
||||
filesMap[path.Join(baseDir, dataModel.Path)] = path.Join(dir, dataModel.Path)
|
||||
}
|
||||
for _, workflow := range schema.Workflows {
|
||||
err = filepath.Walk(path.Join(baseDir, workflow.Path), func(p string, info fs.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
filesMap[p] = path.Join(dir, workflow.Path, path.Base(p))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
}
|
||||
for _, artifact := range schema.Notebooks.Artifacts {
|
||||
filesMap[path.Join(baseDir, artifact.Path)] = path.Join(dir, artifact.Path)
|
||||
}
|
||||
for fToRead, fToWrite := range filesMap {
|
||||
input, err := os.ReadFile(filepath.Clean(fToRead))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err = os.Stat(fToWrite); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(filepath.Dir(fToWrite), os.ModePerm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
err = os.WriteFile(fToWrite, input, 0660)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
zipFilePath := strings.ReplaceAll(yamlBase, ".yaml", ".zip")
|
||||
err = utils.ZipDir(dir, zipFilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zipFilePath, nil
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/internal/context/workspace/application/query/workspace"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
)
|
||||
|
||||
// ListOptions is an options to list workspaces.
|
||||
type ListOptions struct {
|
||||
Page int32
|
||||
Size int32
|
||||
OrderBy string
|
||||
SearchWords []string
|
||||
Ids []string
|
||||
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewListOptions returns a reference to a ListOptions.
|
||||
func NewListOptions(opt *clioptions.GlobalOptions) *ListOptions {
|
||||
return &ListOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdList new a list workspace cmd.
|
||||
func NewCmdList(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewListOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "list workspaces",
|
||||
Long: "list workspaces",
|
||||
Args: cobra.NoArgs,
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().Int32VarP(&o.Page, "page", "p", 1, "The page number")
|
||||
cmd.Flags().Int32VarP(&o.Size, "size", "s", 10, "The page size")
|
||||
cmd.Flags().StringVar(&o.OrderBy, "order-by", o.OrderBy, "The order-by field")
|
||||
cmd.Flags().StringSliceVar(&o.SearchWords, "search-word", o.SearchWords, "The search word")
|
||||
cmd.Flags().StringSliceVar(&o.Ids, "ids", o.Ids, "The ids of the workspace.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *ListOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
if len(o.Ids) > 0 {
|
||||
o.options.Stream.OutputFormat = formatter.JsonFormat
|
||||
} else {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the list options
|
||||
func (o *ListOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the create workspace command
|
||||
func (o *ListOptions) Run(_ []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
|
||||
req := &convert.ListWorkspacesRequest{
|
||||
Page: int(o.Page),
|
||||
Size: int(o.Size),
|
||||
}
|
||||
if len(o.Ids) > 0 {
|
||||
req.IDs = strings.Join(o.Ids, consts.QuerySliceDelimiter)
|
||||
}
|
||||
if o.OrderBy != "" {
|
||||
req.OrderBy = o.OrderBy
|
||||
}
|
||||
if len(o.SearchWords) > 0 {
|
||||
req.SearchWord = strings.Join(o.SearchWords, consts.QuerySliceDelimiter)
|
||||
}
|
||||
resp, err := o.workspaceClient.ListWorkspaces(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.formatter.Write(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetPromptArgs() ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
o.Page, err = prompt.PromptRequiredInt32("Page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Size, err = prompt.PromptRequiredInt32("Size")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orderByFields, err := prompt.PromptStringMultiSelect("OrderBy", 2, []string{workspace.OrderByName, workspace.OrderByCreateTime})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(orderByFields) > 0 {
|
||||
for i, field := range orderByFields {
|
||||
ascending, err := prompt.PromptStringSelect(fmt.Sprintf("%s Ascending", field), 2, []string{consts.ASCOrdering, consts.DESCOrdering})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderByFields[i] += consts.OrderDelimiter + ascending
|
||||
}
|
||||
o.OrderBy = strings.Join(orderByFields, ",")
|
||||
}
|
||||
|
||||
o.SearchWords, err = prompt.PromptStringSlice("SearchWords")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Ids, err = prompt.PromptStringSlice("IDs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// correct formatter
|
||||
if len(o.Ids) > 0 {
|
||||
o.options.Stream.OutputFormat = formatter.JsonFormat
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *ListOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TableFormat
|
||||
}
|
||||
|
||||
// ConvertWorkspaceNameIntoID convert workspace name into id
|
||||
func ConvertWorkspaceNameIntoID(ctx context.Context, workspaceClient factory.WorkspaceClient, name string) (id string, err error) {
|
||||
pageNum := 1
|
||||
pageSize := 100
|
||||
for {
|
||||
var workspaces *convert.ListWorkspacesResponse
|
||||
workspaces, err = workspaceClient.ListWorkspaces(ctx, &convert.ListWorkspacesRequest{
|
||||
Page: pageNum,
|
||||
Size: pageSize,
|
||||
SearchWord: name,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, item := range workspaces.Items {
|
||||
if item.Name == name {
|
||||
id = item.Id
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(workspaces.Items) < pageSize {
|
||||
err = fmt.Errorf("workspace: %s not found", name)
|
||||
return
|
||||
}
|
||||
pageNum++
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/cmd"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
// UpdateOptions is an options to update workspaces.
|
||||
type UpdateOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
|
||||
workspaceClient factory.WorkspaceClient
|
||||
formatter formatter.Formatter
|
||||
|
||||
options *clioptions.GlobalOptions
|
||||
}
|
||||
|
||||
// NewUpdateOptions returns a reference to a UpdateOptions.
|
||||
func NewUpdateOptions(opt *clioptions.GlobalOptions) *UpdateOptions {
|
||||
return &UpdateOptions{
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdUpdate new a update workspace cmd.
|
||||
func NewCmdUpdate(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
o := NewUpdateOptions(opt)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <workspace_name>",
|
||||
Short: "update a workspace",
|
||||
Long: "update a workspace",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: clioptions.GetCommonRunFunc(o),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.Name, "name", "n", o.Name, "The name of the workspace.")
|
||||
cmd.Flags().StringVarP(&o.Description, "description", "d", o.Description, "The description of the workspace.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options.
|
||||
func (o *UpdateOptions) Complete() error {
|
||||
var err error
|
||||
f := factory.NewFactory(&o.options.Client)
|
||||
o.workspaceClient, err = f.WorkspaceClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.options.Stream.OutputFormat == "" {
|
||||
o.options.Stream.OutputFormat = o.GetDefaultFormat()
|
||||
}
|
||||
o.formatter = formatter.NewFormatter(o.options.Stream.OutputFormat, o.options.Stream.Output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validate the list options
|
||||
func (o *UpdateOptions) Validate() error {
|
||||
if err := o.options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run run the update workspace command
|
||||
func (o *UpdateOptions) Run(args []string) error {
|
||||
workspaceName := args[0]
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(o.options.Client.Timeout))
|
||||
defer cancel()
|
||||
|
||||
workspaceID, err := ConvertWorkspaceNameIntoID(ctx, o.workspaceClient, workspaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &convert.UpdateWorkspaceRequest{
|
||||
ID: workspaceID,
|
||||
}
|
||||
if o.Name != "" {
|
||||
req.Name = &o.Name
|
||||
}
|
||||
if o.Description != "" {
|
||||
req.Description = &o.Description
|
||||
}
|
||||
|
||||
_, err = o.workspaceClient.UpdateWorkspace(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *UpdateOptions) GetPromptArgs() ([]string, error) {
|
||||
name, err := cmd.GetWorkspaceName(o.options.Client.Timeout, o.workspaceClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []string{name}, nil
|
||||
}
|
||||
|
||||
func (o *UpdateOptions) GetPromptOptions() error {
|
||||
var err error
|
||||
|
||||
o.Name, err = prompt.PromptOptionalString("Name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Description, err = prompt.PromptOptionalString("Description")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *UpdateOptions) GetDefaultFormat() formatter.Format {
|
||||
return formatter.TextFormat
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package workspace
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
)
|
||||
|
||||
func NewCmdWorkspace(opt *clioptions.GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "workspace",
|
||||
Short: "workspace command",
|
||||
Long: `workspace command`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: prompt.SelectSubCommand,
|
||||
}
|
||||
cmd.AddCommand(NewCmdCreate(opt))
|
||||
cmd.AddCommand(NewCmdList(opt))
|
||||
cmd.AddCommand(NewCmdDelete(opt))
|
||||
cmd.AddCommand(NewCmdUpdate(opt))
|
||||
cmd.AddCommand(NewCmdImport(opt))
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package bioctl
|
|
@ -0,0 +1,207 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
workspaceproto "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
)
|
||||
|
||||
type GetDataModelRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
ID string `path:"id"`
|
||||
}
|
||||
|
||||
func (req *GetDataModelRequest) ToGRPC() *workspaceproto.GetDataModelRequest {
|
||||
return &workspaceproto.GetDataModelRequest{
|
||||
Id: req.ID,
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
}
|
||||
}
|
||||
|
||||
type GetDataModelResponse struct {
|
||||
DataModel *DataModel `json:"dataModel,omitempty"`
|
||||
Headers []string `json:"headers"`
|
||||
}
|
||||
|
||||
func (resp *GetDataModelResponse) FromGRPC(protoResp *workspaceproto.GetDataModelResponse) {
|
||||
resp.DataModel = &DataModel{
|
||||
ID: protoResp.GetDataModel().GetId(),
|
||||
Name: protoResp.GetDataModel().GetName(),
|
||||
RowCount: protoResp.GetDataModel().GetRowCount(),
|
||||
Type: protoResp.GetDataModel().GetType(),
|
||||
}
|
||||
resp.Headers = protoResp.GetHeaders()
|
||||
}
|
||||
|
||||
type ListDataModelsRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
Types []string `query:"types,omitempty"`
|
||||
SearchWord string `query:"searchWord"`
|
||||
IDs []string `query:"ids,omitempty"`
|
||||
}
|
||||
|
||||
func (req *ListDataModelsRequest) ToGRPC() *workspaceproto.ListDataModelsRequest {
|
||||
return &workspaceproto.ListDataModelsRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Types: req.Types,
|
||||
SearchWord: req.SearchWord,
|
||||
Ids: req.IDs,
|
||||
}
|
||||
}
|
||||
|
||||
type ListDataModelsResponse struct {
|
||||
Items []DataModel `json:"Items"`
|
||||
}
|
||||
|
||||
func (resp *ListDataModelsResponse) BriefItems() interface{} {
|
||||
briefItems := make([]DataModel, len(resp.Items))
|
||||
for i, item := range resp.Items {
|
||||
briefItems[i] = item
|
||||
}
|
||||
return reflect.ValueOf(briefItems)
|
||||
}
|
||||
|
||||
func (resp *ListDataModelsResponse) FromGRPC(protoResp *workspaceproto.ListDataModelsResponse) {
|
||||
resp.Items = make([]DataModel, len(protoResp.GetItems()))
|
||||
for i, item := range protoResp.Items {
|
||||
resp.Items[i] = DataModel{
|
||||
ID: item.GetId(),
|
||||
Name: item.GetName(),
|
||||
RowCount: item.GetRowCount(),
|
||||
Type: item.GetType(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DataModel struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
RowCount int64 `json:"rowCount"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ListDataModelRowsRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
ID string `path:"id"`
|
||||
Page int32 `query:"page"`
|
||||
Size int32 `query:"size"`
|
||||
OrderBy string `query:"orderBy"`
|
||||
SearchWord string `query:"searchWord"`
|
||||
InSetIDs []string `query:"inSetIDs"`
|
||||
RowIDs []string `query:"rowIDs"`
|
||||
}
|
||||
|
||||
func (req *ListDataModelRowsRequest) ToGRPC() *workspaceproto.ListDataModelRowsRequest {
|
||||
return &workspaceproto.ListDataModelRowsRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Id: req.ID,
|
||||
Page: req.Page,
|
||||
Size: req.Size,
|
||||
OrderBy: req.OrderBy,
|
||||
SearchWord: req.SearchWord,
|
||||
InSetIDs: req.InSetIDs,
|
||||
RowIDs: req.RowIDs,
|
||||
}
|
||||
}
|
||||
|
||||
type ListDataModelRowsResponse struct {
|
||||
Headers []string `json:"headers"`
|
||||
Rows [][]string `json:"rows"`
|
||||
Page int32 `json:"page"`
|
||||
Size int32 `json:"size"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
func (resp *ListDataModelRowsResponse) FromGRPC(protoResp *workspaceproto.ListDataModelRowsResponse) {
|
||||
resp.Headers = protoResp.GetHeaders()
|
||||
resp.Rows = make([][]string, len(protoResp.Rows))
|
||||
for i, r := range protoResp.Rows {
|
||||
resp.Rows[i] = r.Grids
|
||||
}
|
||||
resp.Page = protoResp.GetPage()
|
||||
resp.Size = protoResp.GetSize()
|
||||
resp.Total = protoResp.GetTotal()
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Grid []Grid `json:"grid"`
|
||||
}
|
||||
|
||||
type Grid struct {
|
||||
Value []byte `json:"value"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type PatchDataModelRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
Name string `json:"name"`
|
||||
Async bool `json:"async"`
|
||||
Headers []string `json:"headers"`
|
||||
Rows [][]string `json:"rows"`
|
||||
}
|
||||
|
||||
func (req *PatchDataModelRequest) ToGRPC() *workspaceproto.PatchDataModelRequest {
|
||||
out := &workspaceproto.PatchDataModelRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Name: req.Name,
|
||||
Async: req.Async,
|
||||
Headers: req.Headers,
|
||||
}
|
||||
out.Rows = make([]*workspaceproto.Row, len(req.Rows))
|
||||
for i, r := range req.Rows {
|
||||
out.Rows[i] = &workspaceproto.Row{
|
||||
Grids: r,
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type PatchDataModelResponse struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (resp *PatchDataModelResponse) FromGRPC(protoResp *workspaceproto.PatchDataModelResponse) {
|
||||
resp.ID = protoResp.GetId()
|
||||
}
|
||||
|
||||
type DeleteDataModelRequest struct {
|
||||
ID string `path:"id"`
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
Headers []string `query:"headers,omitempty"`
|
||||
RowIDs []string `query:"rowIDs,omitempty"`
|
||||
}
|
||||
|
||||
func (req *DeleteDataModelRequest) ToGRPC() *workspaceproto.DeleteDataModelRequest {
|
||||
return &workspaceproto.DeleteDataModelRequest{
|
||||
Id: req.ID,
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Headers: req.Headers,
|
||||
RowIDs: req.RowIDs,
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteDataModelResponse struct{}
|
||||
|
||||
func (resp *DeleteDataModelResponse) FromGRPC(protoResp *workspaceproto.DeleteDataModelResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
type ListAllDataModelRowIDsRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
ID string `path:"id"`
|
||||
}
|
||||
|
||||
func (req *ListAllDataModelRowIDsRequest) ToGRPC() *workspaceproto.ListAllDataModelRowIDsRequest {
|
||||
return &workspaceproto.ListAllDataModelRowIDsRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Id: req.ID,
|
||||
}
|
||||
}
|
||||
|
||||
type ListAllDataModelRowIDsResponse struct {
|
||||
RowIDs []string `json:"rowIDs"`
|
||||
}
|
||||
|
||||
func (resp *ListAllDataModelRowIDsResponse) FromGRPC(protoResp *workspaceproto.ListAllDataModelRowIDsResponse) {
|
||||
resp.RowIDs = protoResp.GetRowIDs()
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package convert
|
||||
|
||||
import submissionproto "github.com/Bio-OS/bioos/internal/context/submission/interface/grpc/proto"
|
||||
|
||||
type ListRunsRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
SubmissionID string `path:"submission_id"`
|
||||
Page int `query:"page"`
|
||||
Size int `query:"size"`
|
||||
OrderBy string `query:"orderBy"`
|
||||
SearchWord string `query:"searchWord"`
|
||||
Status []string `query:"status,omitempty"`
|
||||
IDs []string `query:"ids,omitempty"`
|
||||
}
|
||||
|
||||
func (req *ListRunsRequest) ToGRPC() *submissionproto.ListRunsRequest {
|
||||
return &submissionproto.ListRunsRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
SubmissionID: req.SubmissionID,
|
||||
Page: int32(req.Page),
|
||||
Size: int32(req.Size),
|
||||
OrderBy: req.OrderBy,
|
||||
SearchWord: req.SearchWord,
|
||||
Status: req.Status,
|
||||
Ids: req.IDs,
|
||||
}
|
||||
}
|
||||
|
||||
type ListRunsResponse struct {
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Total int `json:"total"`
|
||||
Items []RunItem `json:"items"`
|
||||
}
|
||||
|
||||
func (resp *ListRunsResponse) FromGRPC(protoResp *submissionproto.ListRunsResponse) {
|
||||
resp.Page = int(protoResp.GetPage())
|
||||
resp.Size = int(protoResp.GetSize())
|
||||
resp.Total = int(protoResp.GetTotal())
|
||||
resp.Items = make([]RunItem, len(protoResp.GetItems()))
|
||||
for i, item := range protoResp.GetItems() {
|
||||
resp.Items[i] = RunItem{
|
||||
ID: item.GetId(),
|
||||
Name: item.GetName(),
|
||||
Status: item.GetStatus(),
|
||||
StartTime: item.GetStartTime(),
|
||||
FinishTime: &item.FinishTime,
|
||||
Duration: item.GetDuration(),
|
||||
EngineRunID: item.GetEngineRunID(),
|
||||
Inputs: item.GetInputs(),
|
||||
Outputs: item.GetOutputs(),
|
||||
TaskStatus: Status{
|
||||
Count: item.GetTaskStatus().GetCount(),
|
||||
Pending: item.GetTaskStatus().GetPending(),
|
||||
Succeeded: item.GetTaskStatus().GetSucceeded(),
|
||||
Failed: item.GetTaskStatus().GetFailed(),
|
||||
Running: item.GetTaskStatus().GetRunning(),
|
||||
Cancelling: item.GetTaskStatus().GetCancelling(),
|
||||
Cancelled: item.GetTaskStatus().GetCancelled(),
|
||||
Queued: item.GetTaskStatus().GetQueued(),
|
||||
Initializing: item.GetTaskStatus().GetInitializing(),
|
||||
},
|
||||
Log: &item.Log,
|
||||
Message: &item.Message,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type RunItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
FinishTime *int64 `json:"finishTime"`
|
||||
Duration int64 `json:"duration"`
|
||||
EngineRunID string `json:"engineRunID"`
|
||||
Inputs string `json:"inputs"`
|
||||
Outputs string `json:"outputs"`
|
||||
TaskStatus Status `json:"taskStatus"`
|
||||
Log *string `json:"log"`
|
||||
Message *string `json:"message"`
|
||||
}
|
||||
|
||||
type CancelRunRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
SubmissionID string `path:"submission_id"`
|
||||
ID string `path:"id"`
|
||||
}
|
||||
|
||||
func (req *CancelRunRequest) ToGRPC() *submissionproto.CancelRunRequest {
|
||||
return &submissionproto.CancelRunRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
SubmissionID: req.SubmissionID,
|
||||
Id: req.ID,
|
||||
}
|
||||
}
|
||||
|
||||
type CancelRunResponse struct {
|
||||
}
|
||||
|
||||
func (resp *CancelRunResponse) FromGRPC(protoResp *submissionproto.CancelRunResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
type ListTasksRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
SubmissionID string `path:"submission_id"`
|
||||
RunID string `path:"run_id"`
|
||||
Page int `query:"page"`
|
||||
Size int `query:"size"`
|
||||
OrderBy string `query:"orderBy"`
|
||||
}
|
||||
|
||||
func (req *ListTasksRequest) ToGRPC() *submissionproto.ListTasksRequest {
|
||||
return &submissionproto.ListTasksRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
SubmissionID: req.SubmissionID,
|
||||
RunID: req.RunID,
|
||||
Page: int32(req.Page),
|
||||
Size: int32(req.Size),
|
||||
OrderBy: req.OrderBy,
|
||||
}
|
||||
}
|
||||
|
||||
type ListTasksResponse struct {
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Total int `json:"total"`
|
||||
Items []TaskItem `json:"items"`
|
||||
}
|
||||
|
||||
func (resp *ListTasksResponse) FromGRPC(protoResp *submissionproto.ListTasksResponse) {
|
||||
resp.Page = int(protoResp.GetPage())
|
||||
resp.Size = int(protoResp.GetSize())
|
||||
resp.Total = int(protoResp.GetTotal())
|
||||
resp.Items = make([]TaskItem, len(protoResp.GetItems()))
|
||||
for i, item := range protoResp.GetItems() {
|
||||
resp.Items[i] = TaskItem{
|
||||
Name: item.GetName(),
|
||||
RunID: item.GetRunID(),
|
||||
Status: item.GetStatus(),
|
||||
StartTime: item.GetStartTime(),
|
||||
FinishTime: &item.FinishTime,
|
||||
Duration: item.GetDuration(),
|
||||
Stdout: item.GetStdout(),
|
||||
Stderr: item.GetStderr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TaskItem struct {
|
||||
Name string `json:"name"`
|
||||
RunID string `json:"runID"`
|
||||
Status string `json:"status"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
FinishTime *int64 `json:"finishTime"`
|
||||
Duration int64 `json:"duration"`
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
submissionproto "github.com/Bio-OS/bioos/internal/context/submission/interface/grpc/proto"
|
||||
)
|
||||
|
||||
type CreateSubmissionRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
Name string `json:"name"`
|
||||
WorkflowID string `json:"workflowID"`
|
||||
Description *string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
Entity *Entity `json:"entity"`
|
||||
ExposedOptions ExposedOptions `json:"exposedOptions"`
|
||||
InOutMaterial *InOutMaterial `json:"inOutMaterial"`
|
||||
}
|
||||
|
||||
func (req *CreateSubmissionRequest) ToGRPC() *submissionproto.CreateSubmissionRequest {
|
||||
return &submissionproto.CreateSubmissionRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Name: req.Name,
|
||||
WorkflowID: req.WorkflowID,
|
||||
Description: pointer.StringDeref(req.Description, ""),
|
||||
Type: req.Type,
|
||||
Entity: &submissionproto.Entity{
|
||||
DataModelID: req.Entity.DataModelID,
|
||||
DataModelRowIDs: req.Entity.DataModelRowIDs,
|
||||
InputsTemplate: req.Entity.InputsTemplate,
|
||||
OutputsTemplate: req.Entity.OutputsTemplate,
|
||||
},
|
||||
ExposedOptions: &submissionproto.ExposedOptions{
|
||||
ReadFromCache: req.ExposedOptions.ReadFromCache,
|
||||
},
|
||||
InOutMaterial: &submissionproto.InOutMaterial{
|
||||
InputsMaterial: req.InOutMaterial.InputsMaterial,
|
||||
OutputsMaterial: req.InOutMaterial.OutputsMaterial,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
DataModelID string `json:"dataModelID"`
|
||||
DataModelRowIDs []string `json:"dataModelRowIDs"`
|
||||
/** 输入配置,json 序列化后的 string
|
||||
采用 json 序列化原因基于以下两点考虑:
|
||||
- thrift/接口设计层面不允许 `Value` 类型不确定
|
||||
- 在 inputs/outputs 层级进行序列化可使得 `bioos-server` 不处理 `Inputs`/`Outputs`(非 `this.xxx` 索引的输入) 就入库/提交给计算引擎,达到透传效果
|
||||
*/
|
||||
InputsTemplate string `json:"inputsTemplate"`
|
||||
/** 输出配置,json 序列化后的 string
|
||||
采用 json 序列化原因基于以下两点考虑:
|
||||
- thrift/接口设计层面不允许 `Value` 类型不确定
|
||||
- 在 inputs/outputs 层级进行序列化可使得 `bioos-server` 不处理 `Inputs`/`Outputs`(非 `this.xxx` 索引的输入) 就入库/提交给计算引擎,达到透传效果
|
||||
*/
|
||||
OutputsTemplate string `json:"outputsTemplate"`
|
||||
}
|
||||
|
||||
type InOutMaterial struct {
|
||||
InputsMaterial string `json:"inputsMaterial"`
|
||||
OutputsMaterial string `json:"outputsMaterial"`
|
||||
}
|
||||
|
||||
type ExposedOptions struct {
|
||||
ReadFromCache bool `json:"readFromCache"`
|
||||
}
|
||||
|
||||
type CreateSubmissionResponse struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (resp *CreateSubmissionResponse) FromGRPC(protoResp *submissionproto.CreateSubmissionResponse) {
|
||||
resp.ID = protoResp.GetId()
|
||||
}
|
||||
|
||||
type CheckSubmissionRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (req *CheckSubmissionRequest) ToGRPC() *submissionproto.CheckSubmissionRequest {
|
||||
return &submissionproto.CheckSubmissionRequest{}
|
||||
}
|
||||
|
||||
type CheckSubmissionResponse struct {
|
||||
IsNameExist bool `json:"isNameExist"`
|
||||
}
|
||||
|
||||
func (resp *CheckSubmissionResponse) FromGRPC(protoResp *submissionproto.CheckSubmissionResponse) {
|
||||
resp.IsNameExist = protoResp.GetIsNameExist()
|
||||
}
|
||||
|
||||
type ListSubmissionsRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
Page int `query:"page"`
|
||||
Size int `query:"size"`
|
||||
OrderBy string `query:"orderBy"`
|
||||
SearchWord string `query:"searchWord"`
|
||||
WorkflowID string `query:"workflowID,omitempty"`
|
||||
Status []string `query:"status,omitempty"`
|
||||
IDs []string `query:"ids,omitempty"`
|
||||
}
|
||||
|
||||
func (req *ListSubmissionsRequest) ToGRPC() *submissionproto.ListSubmissionsRequest {
|
||||
return &submissionproto.ListSubmissionsRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Page: int32(req.Page),
|
||||
Size: int32(req.Size),
|
||||
OrderBy: req.OrderBy,
|
||||
SearchWord: req.SearchWord,
|
||||
WorkflowID: req.WorkflowID,
|
||||
Status: req.Status,
|
||||
Ids: req.IDs,
|
||||
}
|
||||
}
|
||||
|
||||
type ListSubmissionsResponse struct {
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Total int `json:"total"`
|
||||
Items []SubmissionItem `json:"items"`
|
||||
}
|
||||
|
||||
type listSubmissionsResponseBriefItems struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
WorkflowID string `json:"workflowID"`
|
||||
WorkflowVersionID string `json:"versionID"`
|
||||
}
|
||||
|
||||
func (resp *ListSubmissionsResponse) BriefItems() reflect.Value {
|
||||
briefItems := make([]listSubmissionsResponseBriefItems, len(resp.Items))
|
||||
for i, item := range resp.Items {
|
||||
briefItems[i] = listSubmissionsResponseBriefItems{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
Status: item.Status,
|
||||
WorkflowID: item.WorkflowVersion.ID,
|
||||
WorkflowVersionID: item.WorkflowVersion.VersionID,
|
||||
}
|
||||
}
|
||||
return reflect.ValueOf(briefItems)
|
||||
}
|
||||
|
||||
func (resp *ListSubmissionsResponse) FromGRPC(protoResp *submissionproto.ListSubmissionsResponse) {
|
||||
resp.Page = int(protoResp.GetPage())
|
||||
resp.Size = int(protoResp.GetSize())
|
||||
resp.Total = int(protoResp.GetTotal())
|
||||
resp.Items = make([]SubmissionItem, len(protoResp.GetItems()))
|
||||
for i, item := range protoResp.GetItems() {
|
||||
resp.Items[i] = SubmissionItem{
|
||||
ID: item.GetId(),
|
||||
Name: item.GetName(),
|
||||
Description: &item.Description,
|
||||
Status: item.GetStatus(),
|
||||
StartTime: item.GetStartTime(),
|
||||
FinishTime: &item.FinishTime,
|
||||
Duration: item.GetDuration(),
|
||||
WorkflowVersion: WorkflowVersionBrief{
|
||||
item.GetWorkflowVersion().GetId(),
|
||||
item.GetWorkflowVersion().GetVersionID(),
|
||||
},
|
||||
RunStatus: Status{
|
||||
Count: item.GetRunStatus().GetCount(),
|
||||
Pending: item.GetRunStatus().GetPending(),
|
||||
Succeeded: item.GetRunStatus().GetSucceeded(),
|
||||
Failed: item.GetRunStatus().GetFailed(),
|
||||
Running: item.GetRunStatus().GetRunning(),
|
||||
Cancelling: item.GetRunStatus().GetCancelling(),
|
||||
Cancelled: item.GetRunStatus().GetCancelled(),
|
||||
Queued: item.GetRunStatus().GetQueued(),
|
||||
Initializing: item.GetRunStatus().GetInitializing(),
|
||||
},
|
||||
Entity: &Entity{
|
||||
DataModelID: item.GetEntity().GetDataModelID(),
|
||||
DataModelRowIDs: item.GetEntity().GetDataModelRowIDs(),
|
||||
InputsTemplate: item.GetEntity().GetInputsTemplate(),
|
||||
OutputsTemplate: item.GetEntity().GetOutputsTemplate(),
|
||||
},
|
||||
ExposedOptions: ExposedOptions{
|
||||
ReadFromCache: item.GetExposedOptions().GetReadFromCache(),
|
||||
},
|
||||
InOutMaterial: &InOutMaterial{
|
||||
InputsMaterial: item.GetInOutMaterial().GetInputsMaterial(),
|
||||
OutputsMaterial: item.GetInOutMaterial().GetOutputsMaterial(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SubmissionItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
FinishTime *int64 `json:"finishTime"`
|
||||
Duration int64 `json:"duration"`
|
||||
WorkflowVersion WorkflowVersionBrief `json:"workflowVersion"`
|
||||
RunStatus Status `json:"runStatus"`
|
||||
Entity *Entity `json:"entity"`
|
||||
ExposedOptions ExposedOptions `json:"exposedOptions"`
|
||||
InOutMaterial *InOutMaterial `json:"inOutMaterial"`
|
||||
}
|
||||
|
||||
type WorkflowVersionBrief struct {
|
||||
ID string `json:"id"`
|
||||
VersionID string `json:"versionID"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Count int64 `json:"count"`
|
||||
Pending int64 `json:"pending"`
|
||||
Succeeded int64 `json:"succeeded"`
|
||||
Failed int64 `json:"failed"`
|
||||
Running int64 `json:"running"`
|
||||
Cancelling int64 `json:"cancelling"`
|
||||
Cancelled int64 `json:"cancelled"`
|
||||
Queued int64 `json:"queued"`
|
||||
Initializing int64 `json:"initializing"`
|
||||
}
|
||||
|
||||
type DeleteSubmissionRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
ID string `path:"id"`
|
||||
}
|
||||
|
||||
func (req *DeleteSubmissionRequest) ToGRPC() *submissionproto.DeleteSubmissionRequest {
|
||||
return &submissionproto.DeleteSubmissionRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Id: req.ID,
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteSubmissionResponse struct {
|
||||
}
|
||||
|
||||
func (resp *DeleteSubmissionResponse) FromGRPC(protoResp *submissionproto.DeleteSubmissionResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
type CancelSubmissionRequest struct {
|
||||
WorkspaceID string `path:"workspace_id"`
|
||||
ID string `path:"id"`
|
||||
}
|
||||
|
||||
func (req *CancelSubmissionRequest) ToGRPC() *submissionproto.CancelSubmissionRequest {
|
||||
return &submissionproto.CancelSubmissionRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Id: req.ID,
|
||||
}
|
||||
}
|
||||
|
||||
type CancelSubmissionResponse struct {
|
||||
}
|
||||
|
||||
func (resp *CancelSubmissionResponse) FromGRPC(protoResp *submissionproto.CancelSubmissionResponse) {
|
||||
return
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func AssignToHttpRequest(req interface{}, restyRequest *resty.Request) {
|
||||
reqV := reflect.ValueOf(req).Elem()
|
||||
reqT := reqV.Type()
|
||||
var pathParams map[string]string
|
||||
var queryParams url.Values
|
||||
var body map[string]interface{}
|
||||
|
||||
for i := 0; i < reqV.NumField(); i++ {
|
||||
pathTag := reqT.Field(i).Tag.Get("path")
|
||||
if pathTag != "" {
|
||||
if pathParams == nil {
|
||||
pathParams = make(map[string]string)
|
||||
}
|
||||
pathParams[pathTag] = cast.ToString(reqV.Field(i).Interface())
|
||||
}
|
||||
queryTag := reqT.Field(i).Tag.Get("query")
|
||||
if queryTag != "" {
|
||||
if queryParams == nil {
|
||||
queryParams = make(url.Values)
|
||||
}
|
||||
if strings.Contains(queryTag, "omitempty") {
|
||||
if reqV.Field(i).IsZero() {
|
||||
continue
|
||||
}
|
||||
queryTag = strings.Split(queryTag, ",")[0]
|
||||
}
|
||||
queryParams[queryTag] = cast.ToStringSlice(reqV.Field(i).Interface())
|
||||
}
|
||||
bodyTag := reqT.Field(i).Tag.Get("json")
|
||||
if bodyTag != "" {
|
||||
if body == nil {
|
||||
body = make(map[string]interface{})
|
||||
}
|
||||
if strings.Contains(bodyTag, "omitempty") {
|
||||
if reqV.Field(i).IsZero() {
|
||||
continue
|
||||
}
|
||||
bodyTag = strings.Split(bodyTag, ",")[0]
|
||||
}
|
||||
body[bodyTag] = reqV.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
restyRequest.SetPathParams(pathParams).SetQueryParamsFromValues(queryParams).SetBody(body)
|
||||
}
|
||||
|
||||
func AssignFromHttpResponse(restyResp *resty.Response, resp interface{}) error {
|
||||
if restyResp.RawResponse != nil && restyResp.RawResponse.StatusCode >= 400 {
|
||||
return errors.New(string(restyResp.Body()))
|
||||
}
|
||||
err := json.Unmarshal(restyResp.Body(), resp)
|
||||
if err != nil {
|
||||
return errors.New(string(restyResp.Body()))
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,421 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
workspaceproto "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
)
|
||||
|
||||
type CreateWorkflowRequest struct {
|
||||
ID string `json:"id"`
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
Name string `json:"name" validate:"required,resName"`
|
||||
Description string `json:"description" validate:"required,workspaceDesc"`
|
||||
Language string `json:"language" validate:"required,oneof=WDL"`
|
||||
Source string `json:"source" validate:"required,oneof=git"`
|
||||
Url string `json:"url" validate:"required"`
|
||||
Tag string `json:"tag" validate:"required"`
|
||||
Token string `json:"token"`
|
||||
MainWorkflowPath string `json:"mainWorkflowPath" validate:"required"`
|
||||
}
|
||||
|
||||
func (req *CreateWorkflowRequest) ToGRPC() *workspaceproto.CreateWorkflowRequest {
|
||||
return &workspaceproto.CreateWorkflowRequest{
|
||||
Id: &req.ID,
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Name: req.Name,
|
||||
Description: &req.Description,
|
||||
Language: req.Language,
|
||||
Source: req.Source,
|
||||
Url: &req.Url,
|
||||
Tag: &req.Tag,
|
||||
Token: &req.Token,
|
||||
MainWorkflowPath: req.MainWorkflowPath,
|
||||
}
|
||||
}
|
||||
|
||||
type CreateWorkflowResponse struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (resp *CreateWorkflowResponse) FromGRPC(protoResp *workspaceproto.CreateWorkflowResponse) {
|
||||
resp.ID = protoResp.GetId()
|
||||
}
|
||||
|
||||
type GetWorkflowRequest struct {
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
ID string `path:"id"`
|
||||
}
|
||||
|
||||
func (req *GetWorkflowRequest) ToGRPC() *workspaceproto.GetWorkflowRequest {
|
||||
return &workspaceproto.GetWorkflowRequest{
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
Id: req.ID,
|
||||
}
|
||||
}
|
||||
|
||||
type GetWorkflowResponse struct {
|
||||
Workflow WorkflowItem `json:"workflow"`
|
||||
}
|
||||
|
||||
func (resp *GetWorkflowResponse) FromGRPC(protoResp *workspaceproto.GetWorkflowResponse) {
|
||||
resp.Workflow = convertWorkflowItem(protoResp.GetWorkflow())
|
||||
}
|
||||
|
||||
type ListWorkflowsRequest struct {
|
||||
Page int `query:"page"`
|
||||
Size int `query:"size"`
|
||||
OrderBy string `query:"orderBy"`
|
||||
SearchWord string `query:"searchWord"`
|
||||
IDs string `query:"ids"`
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
}
|
||||
|
||||
func (req *ListWorkflowsRequest) ToGRPC() *workspaceproto.ListWorkflowRequest {
|
||||
return &workspaceproto.ListWorkflowRequest{
|
||||
Page: int32(req.Page),
|
||||
Size: int32(req.Size),
|
||||
OrderBy: req.OrderBy,
|
||||
SearchWord: req.SearchWord,
|
||||
Ids: strings.Split(req.IDs, consts.QuerySliceDelimiter),
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
}
|
||||
}
|
||||
|
||||
type ListWorkflowsResponse struct {
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Total int `json:"total"`
|
||||
Items []WorkflowItem `json:"items"`
|
||||
}
|
||||
|
||||
type listWorkflowsResponseBriefItems struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
LatestVersionStatus string `json:"status"`
|
||||
LatestVersionMessage string `json:"message"`
|
||||
LatestVersionLanguage string `json:"language"`
|
||||
LatestVersionLanguageVersion string `json:"languageVersion"`
|
||||
LatestVersionMainWorkflowPath string `json:"mainWorkflowPath"`
|
||||
LatestVersionSource string `json:"source"`
|
||||
}
|
||||
|
||||
func (resp *ListWorkflowsResponse) BriefItems() reflect.Value {
|
||||
briefItems := make([]listWorkflowsResponseBriefItems, len(resp.Items))
|
||||
for i, item := range resp.Items {
|
||||
briefItems[i] = listWorkflowsResponseBriefItems{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
LatestVersionStatus: item.LatestVersion.Status,
|
||||
LatestVersionMessage: item.LatestVersion.Message,
|
||||
LatestVersionLanguage: item.LatestVersion.Language,
|
||||
LatestVersionLanguageVersion: item.LatestVersion.LanguageVersion,
|
||||
LatestVersionMainWorkflowPath: item.LatestVersion.MainWorkflowPath,
|
||||
LatestVersionSource: item.LatestVersion.Source,
|
||||
}
|
||||
}
|
||||
return reflect.ValueOf(briefItems)
|
||||
}
|
||||
|
||||
func (resp *ListWorkflowsResponse) FromGRPC(protoResp *workspaceproto.ListWorkflowResponse) {
|
||||
resp.Page = int(protoResp.Page)
|
||||
resp.Size = int(protoResp.Size)
|
||||
resp.Total = int(protoResp.Total)
|
||||
resp.Items = make([]WorkflowItem, len(protoResp.Items))
|
||||
for i := range protoResp.Items {
|
||||
resp.Items[i] = convertWorkflowItem(protoResp.Items[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type UpdateWorkflowRequest struct {
|
||||
ID string `path:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
}
|
||||
|
||||
func (req *UpdateWorkflowRequest) ToGRPC() *workspaceproto.UpdateWorkflowRequest {
|
||||
return &workspaceproto.UpdateWorkflowRequest{
|
||||
Id: req.ID,
|
||||
Name: &req.Name,
|
||||
Description: &req.Description,
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateWorkflowResponse struct {
|
||||
}
|
||||
|
||||
func (resp *UpdateWorkflowResponse) FromGRPC(protoResp *workspaceproto.UpdateWorkflowResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
type DeleteWorkflowRequest struct {
|
||||
ID string `path:"id"`
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
}
|
||||
|
||||
func (req *DeleteWorkflowRequest) ToGRPC() *workspaceproto.DeleteWorkflowRequest {
|
||||
return &workspaceproto.DeleteWorkflowRequest{
|
||||
Id: req.ID,
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteWorkflowResponse struct {
|
||||
}
|
||||
|
||||
func (resp *DeleteWorkflowResponse) FromGRPC(protoResp *workspaceproto.DeleteWorkflowResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
type GetWorkflowFileRequest struct {
|
||||
ID string `path:"id"`
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
WorkflowID string `path:"workflow-id"`
|
||||
}
|
||||
|
||||
func (req *GetWorkflowFileRequest) ToGRPC() *workspaceproto.GetWorkflowFileRequest {
|
||||
return &workspaceproto.GetWorkflowFileRequest{
|
||||
Id: req.ID,
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
WorkflowID: req.WorkflowID,
|
||||
}
|
||||
}
|
||||
|
||||
type GetWorkflowFileResponse struct {
|
||||
File *WorkflowFile `json:"file"`
|
||||
}
|
||||
|
||||
func (resp *GetWorkflowFileResponse) FromGRPC(protoResp *workspaceproto.GetWorkflowFileResponse) {
|
||||
resp.File = &WorkflowFile{
|
||||
ID: protoResp.GetFile().GetId(),
|
||||
WorkflowVersionID: protoResp.GetFile().GetWorkflowVersionID(),
|
||||
Path: protoResp.GetFile().GetPath(),
|
||||
Content: protoResp.GetFile().GetContent(),
|
||||
CreatedAt: protoResp.GetFile().GetCreatedAt().AsTime(),
|
||||
UpdatedAt: protoResp.GetFile().GetUpdatedAt().AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
type GetWorkflowVersionRequest struct {
|
||||
ID string `path:"id"`
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
WorkflowID string `path:"workflow-id"`
|
||||
}
|
||||
|
||||
func (req *GetWorkflowVersionRequest) ToGRPC() *workspaceproto.GetWorkflowVersionRequest {
|
||||
return &workspaceproto.GetWorkflowVersionRequest{
|
||||
Id: req.ID,
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
WorkflowID: req.WorkflowID,
|
||||
}
|
||||
}
|
||||
|
||||
type GetWorkflowVersionResponse struct {
|
||||
Version WorkflowVersion `json:"version"`
|
||||
}
|
||||
|
||||
func (resp *GetWorkflowVersionResponse) FromGRPC(protoResp *workspaceproto.GetWorkflowVersionResponse) {
|
||||
resp.Version = convertWorkflowVersion(protoResp.GetVersion())
|
||||
}
|
||||
|
||||
type ListWorkflowFilesRequest struct {
|
||||
Page int `query:"page"`
|
||||
Size int `query:"size"`
|
||||
OrderBy string `query:"orderBy"`
|
||||
IDs string `query:"ids"`
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
WorkflowID string `path:"workflow-id"`
|
||||
WorkflowVersionID string `query:"workflowVersionID,omitempty"`
|
||||
}
|
||||
|
||||
func (req *ListWorkflowFilesRequest) ToGRPC() *workspaceproto.ListWorkflowFilesRequest {
|
||||
return &workspaceproto.ListWorkflowFilesRequest{
|
||||
Page: int32(req.Page),
|
||||
Size: int32(req.Size),
|
||||
OrderBy: req.OrderBy,
|
||||
Ids: strings.Split(req.IDs, consts.QuerySliceDelimiter),
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
WorkflowID: req.WorkflowID,
|
||||
WorkflowVersionID: &req.WorkflowVersionID,
|
||||
}
|
||||
}
|
||||
|
||||
type ListWorkflowFilesResponse struct {
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Total int `json:"total"`
|
||||
WorkspaceID string `json:"workspaceID"`
|
||||
WorkflowID string `json:"workflowID"`
|
||||
Items []*WorkflowFile `json:"items"`
|
||||
}
|
||||
|
||||
func (resp *ListWorkflowFilesResponse) FromGRPC(protoResp *workspaceproto.ListWorkflowFilesResponse) {
|
||||
resp.Page = int(protoResp.GetPage())
|
||||
resp.Size = int(protoResp.GetSize())
|
||||
resp.Total = int(protoResp.GetTotal())
|
||||
resp.WorkspaceID = protoResp.GetWorkspaceID()
|
||||
resp.WorkflowID = protoResp.GetWorkflowID()
|
||||
resp.Items = make([]*WorkflowFile, len(protoResp.GetFiles()))
|
||||
for i, f := range protoResp.GetFiles() {
|
||||
resp.Items[i] = &WorkflowFile{
|
||||
ID: f.GetId(),
|
||||
WorkflowVersionID: f.GetWorkflowVersionID(),
|
||||
Path: f.GetPath(),
|
||||
Content: f.GetContent(),
|
||||
CreatedAt: f.GetCreatedAt().AsTime(),
|
||||
UpdatedAt: f.GetUpdatedAt().AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type ListWorkflowVersionsRequest struct {
|
||||
Page int `query:"page"`
|
||||
Size int `query:"size"`
|
||||
OrderBy string `query:"orderBy"`
|
||||
IDs string `query:"ids"`
|
||||
WorkspaceID string `path:"workspace-id"`
|
||||
WorkflowID string `path:"workflow-id"`
|
||||
}
|
||||
|
||||
func (req *ListWorkflowVersionsRequest) ToGRPC() *workspaceproto.ListWorkflowVersionsRequest {
|
||||
return &workspaceproto.ListWorkflowVersionsRequest{
|
||||
Page: int32(req.Page),
|
||||
Size: int32(req.Size),
|
||||
OrderBy: req.OrderBy,
|
||||
Ids: strings.Split(req.IDs, consts.QuerySliceDelimiter),
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
WorkflowID: req.WorkflowID,
|
||||
}
|
||||
}
|
||||
|
||||
type ListWorkflowVersionsResponse struct {
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Total int `json:"total"`
|
||||
WorkspaceID string `json:"workspaceID"`
|
||||
WorkflowID string `json:"workflowID"`
|
||||
Items []WorkflowVersion `json:"items"`
|
||||
}
|
||||
|
||||
func (resp *ListWorkflowVersionsResponse) FromGRPC(protoResp *workspaceproto.ListWorkflowVersionsResponse) {
|
||||
resp.Page = int(protoResp.GetPage())
|
||||
resp.Size = int(protoResp.GetSize())
|
||||
resp.Total = int(protoResp.GetTotal())
|
||||
resp.WorkspaceID = protoResp.GetWorkspaceID()
|
||||
resp.WorkflowID = protoResp.GetWorkflowID()
|
||||
resp.Items = make([]WorkflowVersion, len(protoResp.GetItems()))
|
||||
for i, item := range protoResp.GetItems() {
|
||||
resp.Items[i] = convertWorkflowVersion(item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type WorkflowItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
LatestVersion WorkflowVersion `json:"latestVersion"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt"`
|
||||
}
|
||||
|
||||
type WorkflowVersion struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Language string `json:"language"`
|
||||
LanguageVersion string `json:"languageVersion"`
|
||||
MainWorkflowPath string `json:"mainWorkflowPath"`
|
||||
Inputs []*WorkflowParam `json:"inputs"`
|
||||
Outputs []*WorkflowParam `json:"outputs"`
|
||||
Graph string `json:"graph"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
Source string `json:"source"`
|
||||
Files []*WorkflowFileInfo `json:"files"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt"`
|
||||
}
|
||||
|
||||
type WorkflowFileInfo struct {
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type WorkflowParam struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Optional bool `json:"optional"`
|
||||
Default string `json:"default"`
|
||||
}
|
||||
|
||||
type WorkflowFile struct {
|
||||
ID string `json:"id"`
|
||||
WorkflowVersionID string `json:"workflowVersionID"`
|
||||
Path string `json:"path"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt"`
|
||||
}
|
||||
|
||||
func convertWorkflowItem(workflow *workspaceproto.Workflow) WorkflowItem {
|
||||
item := WorkflowItem{
|
||||
ID: workflow.GetId(),
|
||||
Name: workflow.GetName(),
|
||||
Description: workflow.GetDescription(),
|
||||
LatestVersion: convertWorkflowVersion(workflow.GetLatestVersion()),
|
||||
CreatedAt: workflow.GetCreatedAt().AsTime(),
|
||||
UpdatedAt: workflow.GetUpdatedAt().AsTime(),
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func convertWorkflowVersion(version *workspaceproto.WorkflowVersion) WorkflowVersion {
|
||||
v := WorkflowVersion{
|
||||
ID: version.GetId(),
|
||||
Status: version.GetStatus(),
|
||||
Message: version.GetMessage(),
|
||||
Language: version.GetLanguage(),
|
||||
LanguageVersion: version.GetLanguageVersion(),
|
||||
MainWorkflowPath: version.GetMainWorkflowPath(),
|
||||
Inputs: make([]*WorkflowParam, len(version.GetInputs())),
|
||||
Outputs: make([]*WorkflowParam, len(version.GetOutputs())),
|
||||
Graph: version.GetGraph(),
|
||||
Metadata: version.GetMetadata(),
|
||||
Source: version.GetSource(),
|
||||
Files: make([]*WorkflowFileInfo, len(version.GetFiles())),
|
||||
CreatedAt: version.GetCreatedAt().AsTime(),
|
||||
UpdatedAt: version.GetUpdatedAt().AsTime(),
|
||||
}
|
||||
for i, param := range version.GetInputs() {
|
||||
v.Inputs[i] = &WorkflowParam{
|
||||
Name: param.Name,
|
||||
Type: param.Type,
|
||||
Optional: param.Optional,
|
||||
Default: *param.Default,
|
||||
}
|
||||
}
|
||||
for i, param := range version.GetOutputs() {
|
||||
v.Outputs[i] = &WorkflowParam{
|
||||
Name: param.Name,
|
||||
Type: param.Type,
|
||||
Optional: param.Optional,
|
||||
Default: *param.Default,
|
||||
}
|
||||
}
|
||||
for i, f := range version.GetFiles() {
|
||||
v.Files[i] = &WorkflowFileInfo{
|
||||
ID: f.Id,
|
||||
Path: f.Path,
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
workspaceproto "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
"github.com/Bio-OS/bioos/pkg/consts"
|
||||
)
|
||||
|
||||
type CreateWorkspaceRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Storage *WorkspaceStorage `json:"storage"`
|
||||
}
|
||||
|
||||
func (req *CreateWorkspaceRequest) ToGRPC() *workspaceproto.CreateWorkspaceRequest {
|
||||
return &workspaceproto.CreateWorkspaceRequest{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Storage: &workspaceproto.WorkspaceStorage{
|
||||
Nfs: &workspaceproto.NFSWorkspaceStorage{
|
||||
MountPath: req.Storage.NFS.MountPath,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CreateWorkspaceResponse struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
func (resp *CreateWorkspaceResponse) FromGRPC(protoResp *workspaceproto.CreateWorkspaceResponse) {
|
||||
resp.Id = protoResp.Id
|
||||
}
|
||||
|
||||
type DeleteWorkspaceRequest struct {
|
||||
Id string `path:"id"`
|
||||
}
|
||||
|
||||
func (req *DeleteWorkspaceRequest) ToGRPC() *workspaceproto.DeleteWorkspaceRequest {
|
||||
return &workspaceproto.DeleteWorkspaceRequest{
|
||||
Id: req.Id,
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteWorkspaceResponse struct {
|
||||
}
|
||||
|
||||
func (resp *DeleteWorkspaceResponse) FromGRPC(protoResp *workspaceproto.DeleteWorkspaceResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
type UpdateWorkspaceRequest struct {
|
||||
ID string `path:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
func (req *UpdateWorkspaceRequest) ToGRPC() *workspaceproto.UpdateWorkspaceRequest {
|
||||
return &workspaceproto.UpdateWorkspaceRequest{
|
||||
Id: req.ID,
|
||||
Name: pointer.StringDeref(req.Name, ""),
|
||||
Description: pointer.StringDeref(req.Description, ""),
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateWorkspaceResponse struct {
|
||||
}
|
||||
|
||||
func (resp *UpdateWorkspaceResponse) FromGRPC(protoResp *workspaceproto.UpdateWorkspaceResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
type ListWorkspacesRequest struct {
|
||||
Page int `query:"page"`
|
||||
Size int `query:"size"`
|
||||
OrderBy string `query:"orderBy"`
|
||||
SearchWord string `query:"searchWord"`
|
||||
IDs string `query:"ids"`
|
||||
}
|
||||
|
||||
func (req *ListWorkspacesRequest) ToGRPC() *workspaceproto.ListWorkspaceRequest {
|
||||
r := &workspaceproto.ListWorkspaceRequest{
|
||||
Page: int32(req.Page),
|
||||
Size: int32(req.Size),
|
||||
OrderBy: req.OrderBy,
|
||||
SearchWord: req.SearchWord,
|
||||
}
|
||||
if req.IDs != "" {
|
||||
r.Ids = strings.Split(req.IDs, consts.QuerySliceDelimiter)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type ListWorkspacesResponse struct {
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Total int `json:"total"`
|
||||
Items []WorkspaceItem `json:"items"`
|
||||
}
|
||||
|
||||
type listWorkspacesBriefItems struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func (resp *ListWorkspacesResponse) BriefItems() reflect.Value {
|
||||
briefItems := make([]listWorkspacesBriefItems, len(resp.Items))
|
||||
for i, item := range resp.Items {
|
||||
briefItems[i] = struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}{
|
||||
Id: item.Id,
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
}
|
||||
}
|
||||
return reflect.ValueOf(briefItems)
|
||||
}
|
||||
|
||||
func (resp *ListWorkspacesResponse) FromGRPC(protoResp *workspaceproto.ListWorkspaceResponse) {
|
||||
resp.Page = int(protoResp.Page)
|
||||
resp.Size = int(protoResp.Size)
|
||||
resp.Total = int(protoResp.Total)
|
||||
resp.Items = make([]WorkspaceItem, len(protoResp.GetItems()))
|
||||
for i, item := range protoResp.Items {
|
||||
resp.Items[i] = WorkspaceItem{
|
||||
Id: item.Id,
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
Storage: &WorkspaceStorage{
|
||||
NFS: &NFSWorkspaceStorage{
|
||||
MountPath: item.Storage.Nfs.MountPath,
|
||||
},
|
||||
},
|
||||
CreateTime: item.CreatedAt.GetSeconds(),
|
||||
UpdateTime: item.UpdatedAt.GetSeconds(),
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type WorkspaceItem struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Storage *WorkspaceStorage `json:"storage"`
|
||||
CreateTime int64 `json:"createTime"`
|
||||
UpdateTime int64 `json:"updateTime"`
|
||||
}
|
||||
|
||||
type WorkspaceStorage struct {
|
||||
NFS *NFSWorkspaceStorage `json:"nfs,omitempty"`
|
||||
}
|
||||
|
||||
// For table/text output
|
||||
func (s WorkspaceStorage) String() string {
|
||||
return s.NFS.MountPath
|
||||
}
|
||||
|
||||
type NFSWorkspaceStorage struct {
|
||||
MountPath string `json:"mountPath"`
|
||||
}
|
||||
|
||||
type ImportWorkspaceRequest struct {
|
||||
FilePath string
|
||||
MountPath string `query:"mountPath"`
|
||||
MountType string `query:"mountType"`
|
||||
}
|
||||
|
||||
type ImportWorkspaceResponse struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
func (resp *ImportWorkspaceResponse) FromGRPC(protoResp *workspaceproto.ImportWorkspaceResponse) {
|
||||
resp.Id = protoResp.GetId()
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
workspaceproto "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
)
|
||||
|
||||
type DataModelClient interface {
|
||||
ListDataModels(ctx context.Context, in *convert.ListDataModelsRequest) (*convert.ListDataModelsResponse, error)
|
||||
GetDataModel(ctx context.Context, in *convert.GetDataModelRequest) (*convert.GetDataModelResponse, error)
|
||||
ListDataModelRows(ctx context.Context, in *convert.ListDataModelRowsRequest) (*convert.ListDataModelRowsResponse, error)
|
||||
PatchDataModel(ctx context.Context, in *convert.PatchDataModelRequest) (*convert.PatchDataModelResponse, error)
|
||||
DeleteDataModel(ctx context.Context, in *convert.DeleteDataModelRequest) (*convert.DeleteDataModelResponse, error)
|
||||
ListAllDataModelRowIDs(ctx context.Context, in *convert.ListAllDataModelRowIDsRequest) (*convert.ListAllDataModelRowIDsResponse, error)
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListDataModels(ctx context.Context, in *convert.ListDataModelsRequest) (*convert.ListDataModelsResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewDataModelServiceClient(g.conn).ListDataModels(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListDataModelsResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) GetDataModel(ctx context.Context, in *convert.GetDataModelRequest) (*convert.GetDataModelResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewDataModelServiceClient(g.conn).GetDataModel(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.GetDataModelResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListDataModelRows(ctx context.Context, in *convert.ListDataModelRowsRequest) (*convert.ListDataModelRowsResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewDataModelServiceClient(g.conn).ListDataModelRows(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListDataModelRowsResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) PatchDataModel(ctx context.Context, in *convert.PatchDataModelRequest) (*convert.PatchDataModelResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewDataModelServiceClient(g.conn).PatchDataModel(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.PatchDataModelResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) DeleteDataModel(ctx context.Context, in *convert.DeleteDataModelRequest) (*convert.DeleteDataModelResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewDataModelServiceClient(g.conn).DeleteDataModel(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.DeleteDataModelResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListAllDataModelRowIDs(ctx context.Context, in *convert.ListAllDataModelRowIDsRequest) (*convert.ListAllDataModelRowIDsResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewDataModelServiceClient(g.conn).ListAllDataModelRowIDs(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListAllDataModelRowIDsResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) ListDataModels(ctx context.Context, in *convert.ListDataModelsRequest) (*convert.ListDataModelsResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace_id}/data_model"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListDataModelsResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) GetDataModel(ctx context.Context, in *convert.GetDataModelRequest) (*convert.GetDataModelResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace_id}/data_model/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.GetDataModelResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) ListDataModelRows(ctx context.Context, in *convert.ListDataModelRowsRequest) (*convert.ListDataModelRowsResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace_id}/data_model/{id}/rows"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListDataModelRowsResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) PatchDataModel(ctx context.Context, in *convert.PatchDataModelRequest) (*convert.PatchDataModelResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Patch(h.url("workspace/{workspace_id}/data_model"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.PatchDataModelResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) DeleteDataModel(ctx context.Context, in *convert.DeleteDataModelRequest) (*convert.DeleteDataModelResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Delete(h.url("workspace/{workspace_id}/data_model/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.DeleteDataModelResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) ListAllDataModelRowIDs(ctx context.Context, in *convert.ListAllDataModelRowIDsRequest) (*convert.ListAllDataModelRowIDsResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace_id}/data_model/{id}/rows/ids"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListAllDataModelRowIDsResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
clioptions "github.com/Bio-OS/bioos/internal/bioctl/options"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils"
|
||||
"github.com/Bio-OS/bioos/pkg/client"
|
||||
pkgutils "github.com/Bio-OS/bioos/pkg/utils"
|
||||
)
|
||||
|
||||
type Factory interface {
|
||||
WorkspaceClient() (WorkspaceClient, error)
|
||||
DataModelClient() (DataModelClient, error)
|
||||
WorkflowClient() (WorkflowClient, error)
|
||||
VersionClient() (VersionClient, error)
|
||||
SubmissionClient() (SubmissionClient, error)
|
||||
}
|
||||
|
||||
func NewFactory(opts *clioptions.ClientOptions) Factory {
|
||||
return factoryImpl{
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
var _ Factory = factoryImpl{}
|
||||
|
||||
type factoryImpl struct {
|
||||
opts *clioptions.ClientOptions
|
||||
}
|
||||
|
||||
func (f factoryImpl) newGrpcClient() (*grpcClient, error) {
|
||||
conn, err := pkgutils.GrpcDial(f.opts.ConnectInfo, f.opts.AuthInfo)
|
||||
if err != nil {
|
||||
utils.CheckErr(err)
|
||||
}
|
||||
return &grpcClient{
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f factoryImpl) newHttpClient() (*httpClient, error) {
|
||||
transport, err := pkgutils.NewTransportWithAuth(f.opts.ConnectInfo, f.opts.AuthInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverAddr := f.opts.ServerAddr
|
||||
if f.opts.Insecure {
|
||||
serverAddr = "http://" + serverAddr
|
||||
} else {
|
||||
serverAddr = "https://" + serverAddr
|
||||
}
|
||||
return &httpClient{
|
||||
addr: strings.TrimRight(serverAddr, "/"),
|
||||
rest: resty.NewWithClient(transport.Client()),
|
||||
}, err
|
||||
}
|
||||
|
||||
func (f factoryImpl) WorkspaceClient() (WorkspaceClient, error) {
|
||||
if err := f.opts.Method.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch f.opts.Method {
|
||||
case client.GRPCMethod:
|
||||
return f.newGrpcClient()
|
||||
case client.HTTPMethod:
|
||||
return f.newHttpClient()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f factoryImpl) DataModelClient() (DataModelClient, error) {
|
||||
if err := f.opts.Method.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch f.opts.Method {
|
||||
case client.GRPCMethod:
|
||||
return f.newGrpcClient()
|
||||
case client.HTTPMethod:
|
||||
return f.newHttpClient()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f factoryImpl) VersionClient() (VersionClient, error) {
|
||||
if err := f.opts.Method.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch f.opts.Method {
|
||||
case client.GRPCMethod:
|
||||
return f.newGrpcClient()
|
||||
case client.HTTPMethod:
|
||||
return f.newHttpClient()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f factoryImpl) SubmissionClient() (SubmissionClient, error) {
|
||||
if err := f.opts.Method.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch f.opts.Method {
|
||||
case client.GRPCMethod:
|
||||
return f.newGrpcClient()
|
||||
case client.HTTPMethod:
|
||||
return f.newHttpClient()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f factoryImpl) WorkflowClient() (WorkflowClient, error) {
|
||||
if err := f.opts.Method.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch f.opts.Method {
|
||||
case client.GRPCMethod:
|
||||
return f.newGrpcClient()
|
||||
case client.HTTPMethod:
|
||||
return f.newHttpClient()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type grpcClient struct {
|
||||
conn *grpc.ClientConn
|
||||
}
|
||||
|
||||
type httpClient struct {
|
||||
addr string
|
||||
rest *resty.Client
|
||||
}
|
||||
|
||||
func (h *httpClient) restR(ctx context.Context) *resty.Request {
|
||||
return h.rest.R().SetContext(ctx).ForceContentType("application/json")
|
||||
}
|
||||
|
||||
func (h *httpClient) url(apipath string, pathParams ...interface{}) string {
|
||||
return strings.Join([]string{h.addr, fmt.Sprintf(apipath, pathParams...)}, "/")
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
submissionproto "github.com/Bio-OS/bioos/internal/context/submission/interface/grpc/proto"
|
||||
)
|
||||
|
||||
type SubmissionClient interface {
|
||||
ListSubmissions(ctx context.Context, in *convert.ListSubmissionsRequest) (*convert.ListSubmissionsResponse, error)
|
||||
CreateSubmission(ctx context.Context, in *convert.CreateSubmissionRequest) (*convert.CreateSubmissionResponse, error)
|
||||
DeleteSubmission(ctx context.Context, in *convert.DeleteSubmissionRequest) (*convert.DeleteSubmissionResponse, error)
|
||||
CancelSubmission(ctx context.Context, in *convert.CancelSubmissionRequest) (*convert.CancelSubmissionResponse, error)
|
||||
ListRuns(ctx context.Context, in *convert.ListRunsRequest) (*convert.ListRunsResponse, error)
|
||||
CancelRun(ctx context.Context, in *convert.CancelRunRequest) (*convert.CancelRunResponse, error)
|
||||
ListTasks(ctx context.Context, in *convert.ListTasksRequest) (*convert.ListTasksResponse, error)
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListSubmissions(ctx context.Context, in *convert.ListSubmissionsRequest) (*convert.ListSubmissionsResponse, error) {
|
||||
|
||||
protoResp, err := submissionproto.NewSubmissionServiceClient(g.conn).ListSubmissions(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListSubmissionsResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) CreateSubmission(ctx context.Context, in *convert.CreateSubmissionRequest) (*convert.CreateSubmissionResponse, error) {
|
||||
|
||||
protoResp, err := submissionproto.NewSubmissionServiceClient(g.conn).CreateSubmission(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CreateSubmissionResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) DeleteSubmission(ctx context.Context, in *convert.DeleteSubmissionRequest) (*convert.DeleteSubmissionResponse, error) {
|
||||
|
||||
protoResp, err := submissionproto.NewSubmissionServiceClient(g.conn).DeleteSubmission(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.DeleteSubmissionResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) CancelSubmission(ctx context.Context, in *convert.CancelSubmissionRequest) (*convert.CancelSubmissionResponse, error) {
|
||||
|
||||
protoResp, err := submissionproto.NewSubmissionServiceClient(g.conn).CancelSubmission(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CancelSubmissionResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListRuns(ctx context.Context, in *convert.ListRunsRequest) (*convert.ListRunsResponse, error) {
|
||||
|
||||
protoResp, err := submissionproto.NewSubmissionServiceClient(g.conn).ListRuns(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListRunsResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) CancelRun(ctx context.Context, in *convert.CancelRunRequest) (*convert.CancelRunResponse, error) {
|
||||
|
||||
protoResp, err := submissionproto.NewSubmissionServiceClient(g.conn).CancelRun(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CancelRunResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListTasks(ctx context.Context, in *convert.ListTasksRequest) (*convert.ListTasksResponse, error) {
|
||||
|
||||
protoResp, err := submissionproto.NewSubmissionServiceClient(g.conn).ListTasks(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListTasksResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) ListSubmissions(ctx context.Context, in *convert.ListSubmissionsRequest) (*convert.ListSubmissionsResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace_id}/submission"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListSubmissionsResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) CreateSubmission(ctx context.Context, in *convert.CreateSubmissionRequest) (*convert.CreateSubmissionResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Post(h.url("workspace/{workspace_id}/submission"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CreateSubmissionResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) DeleteSubmission(ctx context.Context, in *convert.DeleteSubmissionRequest) (*convert.DeleteSubmissionResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Delete(h.url("workspace/{workspace_id}/submission/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.DeleteSubmissionResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) CancelSubmission(ctx context.Context, in *convert.CancelSubmissionRequest) (*convert.CancelSubmissionResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Post(h.url("workspace/{workspace_id}/submission/{id}/cancel"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CancelSubmissionResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) ListRuns(ctx context.Context, in *convert.ListRunsRequest) (*convert.ListRunsResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace_id}/submission/{submission_id}/run"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListRunsResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) CancelRun(ctx context.Context, in *convert.CancelRunRequest) (*convert.CancelRunResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Post(h.url("workspace/{workspace_id}/submission/{submission_id}/run/{id}/cancel"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CancelRunResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) ListTasks(ctx context.Context, in *convert.ListTasksRequest) (*convert.ListTasksResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace_id}/submission/{submission_id}/run/{run_id}/task"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListTasksResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/protocol/consts"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
workspaceproto "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
"github.com/Bio-OS/bioos/pkg/version"
|
||||
)
|
||||
|
||||
type VersionClient interface {
|
||||
Version(ctx context.Context) (*version.Info, error)
|
||||
}
|
||||
|
||||
func (h *httpClient) Version(ctx context.Context) (*version.Info, error) {
|
||||
req := h.restR(ctx)
|
||||
httpResp, err := req.Get(h.url("version"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if httpResp.StatusCode() != consts.StatusOK {
|
||||
return nil, fmt.Errorf("status code is %d", httpResp.StatusCode())
|
||||
}
|
||||
var info *version.Info
|
||||
err = json.Unmarshal(httpResp.Body(), &info)
|
||||
return info, err
|
||||
|
||||
}
|
||||
|
||||
func (g *grpcClient) Version(ctx context.Context) (*version.Info, error) {
|
||||
cli := workspaceproto.NewVersionServiceClient(g.conn)
|
||||
response, err := cli.Version(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &version.Info{
|
||||
Version: response.Version,
|
||||
GitBranch: response.GitBranch,
|
||||
GitCommit: response.GitCommit,
|
||||
GitTreeState: response.GitTreeState,
|
||||
BuildTime: response.BuildTime,
|
||||
GoVersion: response.GoVersion,
|
||||
Compiler: response.Compiler,
|
||||
Platform: response.Platform,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
workspaceproto "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
)
|
||||
|
||||
type WorkflowClient interface {
|
||||
GetWorkflow(ctx context.Context, in *convert.GetWorkflowRequest) (*convert.GetWorkflowResponse, error)
|
||||
GetWorkflowVersion(ctx context.Context, in *convert.GetWorkflowVersionRequest) (*convert.GetWorkflowVersionResponse, error)
|
||||
ListWorkflowFiles(ctx context.Context, in *convert.ListWorkflowFilesRequest) (*convert.ListWorkflowFilesResponse, error)
|
||||
CreateWorkflow(ctx context.Context, in *convert.CreateWorkflowRequest) (*convert.CreateWorkflowResponse, error)
|
||||
DeleteWorkflow(ctx context.Context, in *convert.DeleteWorkflowRequest) (*convert.DeleteWorkflowResponse, error)
|
||||
UpdateWorkflow(ctx context.Context, in *convert.UpdateWorkflowRequest) (*convert.UpdateWorkflowResponse, error)
|
||||
ListWorkflow(ctx context.Context, in *convert.ListWorkflowsRequest) (*convert.ListWorkflowsResponse, error)
|
||||
}
|
||||
|
||||
func (g *grpcClient) GetWorkflow(ctx context.Context, in *convert.GetWorkflowRequest) (*convert.GetWorkflowResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkflowServiceClient(g.conn).GetWorkflow(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.GetWorkflowResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) GetWorkflowVersion(ctx context.Context, in *convert.GetWorkflowVersionRequest) (*convert.GetWorkflowVersionResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkflowServiceClient(g.conn).GetWorkflowVersion(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.GetWorkflowVersionResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListWorkflowFiles(ctx context.Context, in *convert.ListWorkflowFilesRequest) (*convert.ListWorkflowFilesResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkflowServiceClient(g.conn).ListWorkflowFiles(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListWorkflowFilesResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) CreateWorkflow(ctx context.Context, in *convert.CreateWorkflowRequest) (*convert.CreateWorkflowResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkflowServiceClient(g.conn).CreateWorkflow(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CreateWorkflowResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) DeleteWorkflow(ctx context.Context, in *convert.DeleteWorkflowRequest) (*convert.DeleteWorkflowResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkflowServiceClient(g.conn).DeleteWorkflow(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.DeleteWorkflowResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) UpdateWorkflow(ctx context.Context, in *convert.UpdateWorkflowRequest) (*convert.UpdateWorkflowResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkflowServiceClient(g.conn).UpdateWorkflow(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.UpdateWorkflowResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListWorkflow(ctx context.Context, in *convert.ListWorkflowsRequest) (*convert.ListWorkflowsResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkflowServiceClient(g.conn).ListWorkflow(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListWorkflowsResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) GetWorkflow(ctx context.Context, in *convert.GetWorkflowRequest) (*convert.GetWorkflowResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace-id}/workflow/{workflow-id}/file/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.GetWorkflowResponse{}
|
||||
err = convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (h *httpClient) GetWorkflowVersion(ctx context.Context, in *convert.GetWorkflowVersionRequest) (*convert.GetWorkflowVersionResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace-id}/workflow/{workflow-id}/version/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.GetWorkflowVersionResponse{}
|
||||
err = convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (h *httpClient) ListWorkflowFiles(ctx context.Context, in *convert.ListWorkflowFilesRequest) (*convert.ListWorkflowFilesResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace-id}/workflow/{workflow-id}/file"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListWorkflowFilesResponse{}
|
||||
err = convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (h *httpClient) CreateWorkflow(ctx context.Context, in *convert.CreateWorkflowRequest) (*convert.CreateWorkflowResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Post(h.url("workspace/{workspace-id}/workflow"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CreateWorkflowResponse{}
|
||||
err = convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (h *httpClient) DeleteWorkflow(ctx context.Context, in *convert.DeleteWorkflowRequest) (*convert.DeleteWorkflowResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Delete(h.url("workspace/{workspace-id}/workflow/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.DeleteWorkflowResponse{}
|
||||
err = convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (h *httpClient) UpdateWorkflow(ctx context.Context, in *convert.UpdateWorkflowRequest) (*convert.UpdateWorkflowResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Patch(h.url("workspace/{workspace-id}/workflow/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.UpdateWorkflowResponse{}
|
||||
err = convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (h *httpClient) ListWorkflow(ctx context.Context, in *convert.ListWorkflowsRequest) (*convert.ListWorkflowsResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace/{workspace-id}/workflow"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListWorkflowsResponse{}
|
||||
err = convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, err
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/factory/convert"
|
||||
workspaceproto "github.com/Bio-OS/bioos/internal/context/workspace/interface/grpc/proto"
|
||||
)
|
||||
|
||||
type WorkspaceClient interface {
|
||||
CreateWorkspace(ctx context.Context, in *convert.CreateWorkspaceRequest) (*convert.CreateWorkspaceResponse, error)
|
||||
DeleteWorkspace(ctx context.Context, in *convert.DeleteWorkspaceRequest) (*convert.DeleteWorkspaceResponse, error)
|
||||
UpdateWorkspace(ctx context.Context, in *convert.UpdateWorkspaceRequest) (*convert.UpdateWorkspaceResponse, error)
|
||||
ListWorkspaces(ctx context.Context, in *convert.ListWorkspacesRequest) (*convert.ListWorkspacesResponse, error)
|
||||
ImportWorkspace(ctx context.Context, in *convert.ImportWorkspaceRequest) (*convert.ImportWorkspaceResponse, error)
|
||||
}
|
||||
|
||||
func (g *grpcClient) CreateWorkspace(ctx context.Context, in *convert.CreateWorkspaceRequest) (*convert.CreateWorkspaceResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkspaceServiceClient(g.conn).CreateWorkspace(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CreateWorkspaceResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) DeleteWorkspace(ctx context.Context, in *convert.DeleteWorkspaceRequest) (*convert.DeleteWorkspaceResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkspaceServiceClient(g.conn).DeleteWorkspace(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.DeleteWorkspaceResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) UpdateWorkspace(ctx context.Context, in *convert.UpdateWorkspaceRequest) (*convert.UpdateWorkspaceResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkspaceServiceClient(g.conn).UpdateWorkspace(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.UpdateWorkspaceResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) ListWorkspaces(ctx context.Context, in *convert.ListWorkspacesRequest) (*convert.ListWorkspacesResponse, error) {
|
||||
|
||||
protoResp, err := workspaceproto.NewWorkspaceServiceClient(g.conn).ListWorkspace(ctx, in.ToGRPC())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListWorkspacesResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) ImportWorkspace(ctx context.Context, in *convert.ImportWorkspaceRequest) (*convert.ImportWorkspaceResponse, error) {
|
||||
client := workspaceproto.NewWorkspaceServiceClient(g.conn)
|
||||
stream, err := client.ImportWorkspace(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := os.Open(in.FilePath)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Maximum 1KB size per stream.
|
||||
buf := make([]byte, 1024)
|
||||
|
||||
for {
|
||||
num, err := file.Read(buf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := stream.Send(&workspaceproto.ImportWorkspaceRequest{
|
||||
FileName: path.Base(in.FilePath),
|
||||
Content: buf[:num],
|
||||
Storage: &workspaceproto.WorkspaceStorage{
|
||||
Nfs: &workspaceproto.NFSWorkspaceStorage{
|
||||
MountPath: in.MountPath,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
protoResp, err := stream.CloseAndRecv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := &convert.ImportWorkspaceResponse{}
|
||||
out.FromGRPC(protoResp)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) CreateWorkspace(ctx context.Context, in *convert.CreateWorkspaceRequest) (*convert.CreateWorkspaceResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Post(h.url("workspace"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.CreateWorkspaceResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) DeleteWorkspace(ctx context.Context, in *convert.DeleteWorkspaceRequest) (*convert.DeleteWorkspaceResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Delete(h.url("workspace/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.DeleteWorkspaceResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) UpdateWorkspace(ctx context.Context, in *convert.UpdateWorkspaceRequest) (*convert.UpdateWorkspaceResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Patch(h.url("workspace/{id}"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.UpdateWorkspaceResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) ListWorkspaces(ctx context.Context, in *convert.ListWorkspacesRequest) (*convert.ListWorkspacesResponse, error) {
|
||||
req := h.restR(ctx)
|
||||
convert.AssignToHttpRequest(in, req)
|
||||
httpResp, err := req.Get(h.url("workspace"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ListWorkspacesResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) ImportWorkspace(ctx context.Context, in *convert.ImportWorkspaceRequest) (*convert.ImportWorkspaceResponse, error) {
|
||||
req := h.rest.R().SetContext(ctx).ForceContentType("multipart/form-data").SetQueryParams(map[string]string{
|
||||
"mountPath": in.MountPath,
|
||||
"mountType": in.MountType,
|
||||
})
|
||||
file, err := os.Open(in.FilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
req.SetFile("file", path.Base(in.FilePath))
|
||||
httpResp, err := req.Put(h.url("workspace"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &convert.ImportWorkspaceResponse{}
|
||||
convert.AssignFromHttpResponse(httpResp, out)
|
||||
return out, nil
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package options
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/formatter"
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils/prompt"
|
||||
"github.com/Bio-OS/bioos/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigFlagName = "config"
|
||||
DefaultConfigName = "bioctl.yaml"
|
||||
)
|
||||
|
||||
var DefaultConfig = GlobalOptions{
|
||||
Client: ClientOptions{
|
||||
Insecure: true,
|
||||
Method: client.HTTPMethod,
|
||||
Timeout: 30,
|
||||
},
|
||||
Stream: StreamOptions{
|
||||
Input: os.Stdin,
|
||||
Output: os.Stdout,
|
||||
ErrorOutput: os.Stderr,
|
||||
},
|
||||
}
|
||||
|
||||
var CfgFile string
|
||||
|
||||
func init() {
|
||||
pflag.StringVarP(&CfgFile, ConfigFlagName, "c", CfgFile, "Read bioctl configuration from specified `FILE`, "+
|
||||
"support JSON, YAML formats.")
|
||||
}
|
||||
|
||||
type ClientOptions = client.Options
|
||||
|
||||
type GlobalOptions struct {
|
||||
Client ClientOptions `json:"client" mapstructure:"client"`
|
||||
Stream StreamOptions `json:"stream" mapstructure:"stream"`
|
||||
}
|
||||
|
||||
func (o GlobalOptions) Validate() error {
|
||||
if err := o.Client.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.Stream.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StreamOptions struct {
|
||||
// input stream, eg: os.Stdin
|
||||
Input io.Reader `json:"input,omitempty"`
|
||||
// output stream, eg: os.Stdout
|
||||
Output io.Writer `json:"output,omitempty"`
|
||||
// error stream, eg: os.Stderr
|
||||
ErrorOutput io.Writer `json:"errorOutput,omitempty"`
|
||||
// output format, eg: text,table,json
|
||||
OutputFormat formatter.Format `json:"format,omitempty" mapstructure:"format,omitempty"`
|
||||
}
|
||||
|
||||
func (o StreamOptions) Validate() error {
|
||||
if err := o.OutputFormat.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadConfigFile load config file
|
||||
func LoadConfigFile(configFile string, opt *GlobalOptions) {
|
||||
if configFile != "" {
|
||||
viper.SetConfigFile(configFile)
|
||||
} else {
|
||||
viper.SetConfigName(DefaultConfigName)
|
||||
}
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath("conf")
|
||||
viper.AddConfigPath(filepath.Join(homedir.HomeDir(), ".bioctl"))
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigType("yaml") // set the type of the configuration to yaml.
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
utils.CheckErr(err)
|
||||
}
|
||||
|
||||
//stream.key should only come from command line, thus delete it here
|
||||
if viper.Get("stream") != nil {
|
||||
delete(viper.Get("stream").(map[string]interface{}), "format")
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(&opt); err != nil {
|
||||
utils.CheckErr(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetConfigEnv() {
|
||||
viper.SetEnvPrefix("BIO") // set ENVIRONMENT variables prefix to BIO.
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
||||
}
|
||||
|
||||
type Options interface {
|
||||
Complete() error
|
||||
Validate() error
|
||||
Run(args []string) error
|
||||
GetPromptArgs() ([]string, error)
|
||||
GetPromptOptions() error
|
||||
GetDefaultFormat() formatter.Format
|
||||
}
|
||||
|
||||
func GetCommonRunFunc(o Options) func(cmd *cobra.Command, args []string) {
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
utils.CheckErr(o.Complete())
|
||||
|
||||
var err error
|
||||
if len(args) > 0 {
|
||||
if args[0] == prompt.NeedPromptFlag {
|
||||
args, err = o.GetPromptArgs()
|
||||
utils.CheckErr(err)
|
||||
utils.CheckErr(o.GetPromptOptions())
|
||||
}
|
||||
}
|
||||
|
||||
utils.CheckErr(o.Validate())
|
||||
utils.CheckErr(o.Run(args))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package bioctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
profileName string
|
||||
profileOutput string
|
||||
)
|
||||
|
||||
func addProfilingFlags(flags *pflag.FlagSet) {
|
||||
flags.StringVar(&profileName, "profile", "none", "Name of profile to capture. One of (none|cpu|heap|goroutine|threadcreate|block|mutex)")
|
||||
flags.StringVar(&profileOutput, "profile-output", "profile.pprof", "Name of the file to write the profile to")
|
||||
}
|
||||
func initProfiling() error {
|
||||
var (
|
||||
f *os.File
|
||||
err error
|
||||
)
|
||||
switch profileName {
|
||||
case "none":
|
||||
return nil
|
||||
case "cpu":
|
||||
f, err = os.Create(profileOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = pprof.StartCPUProfile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Block and mutex profiles need a call to Set{Block,Mutex}ProfileRate to
|
||||
// output anything. We choose to sample all events.
|
||||
case "block":
|
||||
runtime.SetBlockProfileRate(1)
|
||||
case "mutex":
|
||||
runtime.SetMutexProfileFraction(1)
|
||||
default:
|
||||
// Check the profile name is valid.
|
||||
if profile := pprof.Lookup(profileName); profile == nil {
|
||||
return fmt.Errorf("unknown profile '%s'", profileName)
|
||||
}
|
||||
}
|
||||
|
||||
// If the command is interrupted before the end (ctrl-c), flush the
|
||||
// profiling files
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
f.Close()
|
||||
flushProfiling()
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func flushProfiling() error {
|
||||
switch profileName {
|
||||
case "none":
|
||||
return nil
|
||||
case "cpu":
|
||||
pprof.StopCPUProfile()
|
||||
case "heap":
|
||||
runtime.GC()
|
||||
fallthrough
|
||||
default:
|
||||
profile := pprof.Lookup(profileName)
|
||||
if profile == nil {
|
||||
return nil
|
||||
}
|
||||
f, err := os.Create(profileOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
profile.WriteTo(f, 0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type Formatter interface {
|
||||
Write(interface{})
|
||||
}
|
||||
|
||||
func NewFormatter(format Format, output io.Writer) Formatter {
|
||||
switch format {
|
||||
case TextFormat:
|
||||
return NewText(output)
|
||||
case TableFormat:
|
||||
return NewTable(output)
|
||||
case JsonFormat:
|
||||
return NewJson(output)
|
||||
case YamlFormat:
|
||||
return NewYaml(output)
|
||||
default:
|
||||
return NewText(output)
|
||||
}
|
||||
}
|
||||
|
||||
func NewText(output io.Writer) Formatter {
|
||||
return &Text{output}
|
||||
}
|
||||
|
||||
func NewTable(output io.Writer) Formatter {
|
||||
return &Table{
|
||||
*tabwriter.NewWriter(output, 0, 8, 0, tableDelimiter[0], 0),
|
||||
}
|
||||
}
|
||||
|
||||
func NewJson(output io.Writer) Formatter {
|
||||
return &Json{output}
|
||||
}
|
||||
func NewYaml(output io.Writer) Formatter {
|
||||
return &Yaml{output}
|
||||
}
|
||||
|
||||
type Format string
|
||||
|
||||
const (
|
||||
TextFormat Format = "text"
|
||||
TableFormat Format = "table"
|
||||
JsonFormat Format = "json"
|
||||
YamlFormat Format = "yaml"
|
||||
)
|
||||
|
||||
func (f Format) Validate() error {
|
||||
switch f {
|
||||
case TextFormat, TableFormat, JsonFormat, YamlFormat:
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid formatter type. Valid formatter types are 'text', 'table', or 'json'")
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package formatter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Json struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (f *Json) write(bytes []byte) {
|
||||
_, _ = fmt.Fprint(f.writer, string(bytes))
|
||||
}
|
||||
|
||||
func (f *Json) newLine() {
|
||||
_, _ = fmt.Fprintln(f.writer)
|
||||
}
|
||||
|
||||
func (f *Json) Write(o interface{}) {
|
||||
jsons, _ := json.MarshalIndent(o, "", "\t")
|
||||
|
||||
f.write(jsons)
|
||||
f.newLine()
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
writer tabwriter.Writer
|
||||
}
|
||||
|
||||
const (
|
||||
noPrefix = ""
|
||||
emptyVal = "-"
|
||||
prefixNameDivider = "-"
|
||||
tableDelimiter = "\t"
|
||||
)
|
||||
|
||||
// ListResult only applies for table formatter
|
||||
type ListResult interface {
|
||||
BriefItems() reflect.Value
|
||||
}
|
||||
|
||||
var listResponseType = reflect.TypeOf(new(ListResult)).Elem()
|
||||
|
||||
func (f *Table) Write(o interface{}) {
|
||||
oType := reflect.TypeOf(o)
|
||||
if valid := f.validate(oType); valid {
|
||||
val := reflect.ValueOf(o)
|
||||
if !f.isSlice(oType) {
|
||||
if val.Type().Implements(listResponseType) {
|
||||
val = o.(ListResult).BriefItems()
|
||||
}
|
||||
}
|
||||
|
||||
f.writeHeader(val)
|
||||
f.writeValue(val)
|
||||
f.writer.Flush()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (f *Table) validate(val reflect.Type) bool {
|
||||
if f.isSlice(val) {
|
||||
return f.validateStruct(val.Elem())
|
||||
} else if f.isStruct(val) {
|
||||
return f.validateStruct(val)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Table) validateStruct(val reflect.Type) bool {
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
structFieldType := val.Field(i).Type
|
||||
if f.isStruct(structFieldType) {
|
||||
return false
|
||||
} else if f.isSlice(structFieldType) && !f.validateSlice(structFieldType.Elem()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Table) validateSlice(val reflect.Type) bool {
|
||||
if f.isSimpleType(val) {
|
||||
return true
|
||||
} else if f.isStruct(val) {
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
structFieldType := val.Field(i).Type
|
||||
if !f.isSimpleType(structFieldType) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Table) writeHeader(value reflect.Value) {
|
||||
f.write(strings.Join(f.getHeader(noPrefix, value.Type()), tableDelimiter))
|
||||
f.newLine()
|
||||
}
|
||||
|
||||
func (f *Table) getHeader(prefix string, value reflect.Type) []string {
|
||||
if f.isStruct(value) {
|
||||
headers := f.getStructHeaders(prefix, value)
|
||||
return headers
|
||||
} else if f.isSlice(value) {
|
||||
headers := f.getHeader(prefix, value.Elem())
|
||||
return headers
|
||||
} else {
|
||||
return []string{prefix + value.Name()}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Table) getStructHeaders(prefix string, val reflect.Type) []string {
|
||||
var headers []string
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
structFieldType := val.Field(i).Type
|
||||
if f.isSlice(structFieldType) {
|
||||
headers = append(headers, f.getHeader(prefix+val.Field(i).Name+prefixNameDivider, val.Field(i).Type.Elem())...)
|
||||
} else {
|
||||
headers = append(headers, prefix+val.Field(i).Name)
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func (f *Table) writeValue(value reflect.Value) {
|
||||
val := reflect.Indirect(value)
|
||||
if f.isSimpleType(value.Type()) {
|
||||
f.write(fmt.Sprint(value))
|
||||
} else if f.isStruct(value.Type()) {
|
||||
maxCollectionSize := f.getMaxSliceSize(value)
|
||||
if maxCollectionSize == 0 {
|
||||
f.write(f.getSimpleStructOutput(val))
|
||||
} else {
|
||||
f.write(f.getSliceOutput(maxCollectionSize, value))
|
||||
}
|
||||
} else {
|
||||
f.writeCollection(value)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Table) writeCollection(value reflect.Value) {
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
maxCollectionSize := f.getMaxSliceSize(value.Index(i))
|
||||
if maxCollectionSize == 0 {
|
||||
f.write(f.getSimpleStructOutput(value.Index(i)))
|
||||
} else {
|
||||
f.write(f.getSliceOutput(maxCollectionSize, value.Index(i)))
|
||||
}
|
||||
f.newLine()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Table) getMaxSliceSize(value reflect.Value) int {
|
||||
maxSize := 0
|
||||
for i := 0; i < value.Type().NumField(); i++ {
|
||||
field := value.Field(i)
|
||||
if f.isSlice(field.Type()) && field.Len() > maxSize {
|
||||
maxSize = field.Len()
|
||||
}
|
||||
}
|
||||
return maxSize
|
||||
}
|
||||
|
||||
func (f *Table) getSliceOutput(maxCollectionSize int, value reflect.Value) string {
|
||||
val := reflect.Indirect(value)
|
||||
var outputRowList []string
|
||||
output := ""
|
||||
|
||||
for rowIndex := 0; rowIndex < maxCollectionSize; rowIndex++ {
|
||||
for structIndex := 0; structIndex < val.Type().NumField(); structIndex++ {
|
||||
structField := val.Field(structIndex)
|
||||
if f.isSimpleType(structField.Type()) {
|
||||
outputRowList = f.appendSimpleValue(structField, outputRowList, rowIndex)
|
||||
} else if f.isStruct(structField.Type().Elem()) {
|
||||
outputRowList = f.appendSliceStructRow(structField, outputRowList, rowIndex)
|
||||
} else if f.isSlice(structField.Type()) {
|
||||
outputRowList = f.appendSimpleSlice(structField, outputRowList, rowIndex)
|
||||
} else {
|
||||
outputRowList = append(outputRowList, fmt.Sprint(structField))
|
||||
}
|
||||
}
|
||||
output += strings.Join(outputRowList, tableDelimiter) + "\n"
|
||||
outputRowList = outputRowList[:0]
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (f *Table) appendSliceStructRow(field reflect.Value, outputList []string, row int) []string {
|
||||
for k := 0; k < field.Type().Elem().NumField(); k++ {
|
||||
if row >= field.Len() {
|
||||
outputList = append(outputList, emptyVal)
|
||||
} else {
|
||||
outputList = append(outputList, fmt.Sprint(field.Index(row).Field(k)))
|
||||
}
|
||||
}
|
||||
return outputList
|
||||
}
|
||||
|
||||
func (f *Table) appendSimpleSlice(field reflect.Value, outputList []string, row int) []string {
|
||||
if row >= field.Len() {
|
||||
outputList = append(outputList, emptyVal)
|
||||
} else {
|
||||
outputList = append(outputList, fmt.Sprint(field.Index(row)))
|
||||
}
|
||||
return outputList
|
||||
}
|
||||
|
||||
func (f *Table) appendSimpleValue(field reflect.Value, outputList []string, row int) []string {
|
||||
if row > 0 {
|
||||
outputList = append(outputList, emptyVal)
|
||||
} else {
|
||||
outputList = append(outputList, fmt.Sprint(field))
|
||||
}
|
||||
return outputList
|
||||
}
|
||||
|
||||
func (f *Table) getSimpleStructOutput(val reflect.Value) string {
|
||||
var outputList []string
|
||||
for i := 0; i < val.Type().NumField(); i++ {
|
||||
outputList = append(outputList, fmt.Sprint(val.Field(i)))
|
||||
}
|
||||
return strings.Join(outputList, tableDelimiter)
|
||||
}
|
||||
|
||||
func (f *Table) isSimpleType(value reflect.Type) bool {
|
||||
return !(value.Kind() == reflect.Slice || value.Kind() == reflect.Struct)
|
||||
}
|
||||
|
||||
func (f *Table) isSlice(value reflect.Type) bool {
|
||||
return value.Kind() == reflect.Slice
|
||||
}
|
||||
|
||||
func (f *Table) isStruct(value reflect.Type) bool {
|
||||
return value.Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func (f *Table) write(v interface{}) {
|
||||
_, _ = fmt.Fprint(&f.writer, v)
|
||||
}
|
||||
|
||||
func (f *Table) newLine() {
|
||||
_, _ = fmt.Fprintln(&f.writer)
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const delimiter = "\t"
|
||||
|
||||
type Text struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (f *Text) Write(o interface{}) {
|
||||
f.writeValue(reflect.ValueOf(o))
|
||||
}
|
||||
|
||||
func (f *Text) writeValue(value reflect.Value) {
|
||||
switch value.Kind() {
|
||||
case reflect.Struct:
|
||||
f.writeStruct(value)
|
||||
case reflect.Ptr:
|
||||
f.writeValue(value.Elem())
|
||||
case reflect.Slice:
|
||||
f.writeSliceValue(value)
|
||||
default:
|
||||
f.writeOther(value)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Text) writeOther(value reflect.Value) {
|
||||
f.write(value)
|
||||
f.newLine()
|
||||
}
|
||||
|
||||
func (f *Text) writeSliceValue(sliceValue reflect.Value) {
|
||||
for i := 0; i < sliceValue.Len(); i++ {
|
||||
itemValue := sliceValue.Index(i)
|
||||
f.writeValue(itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Text) writeStruct(value reflect.Value) {
|
||||
f.writeSimpleFields(value)
|
||||
f.writeCompoundFields(value)
|
||||
}
|
||||
|
||||
func (f *Text) writeSimpleFields(value reflect.Value) {
|
||||
id := strings.ToUpper(value.Type().Name())
|
||||
f.write(id)
|
||||
fieldNames := getFieldNames(value)
|
||||
for _, fieldName := range fieldNames {
|
||||
field := value.FieldByName(fieldName)
|
||||
if isSimpleType(field) {
|
||||
f.write(delimiter)
|
||||
f.write(fmt.Sprint(field))
|
||||
}
|
||||
}
|
||||
f.newLine()
|
||||
}
|
||||
|
||||
func isSimpleType(value reflect.Value) bool {
|
||||
return !(value.Kind() == reflect.Struct || value.Kind() == reflect.Slice)
|
||||
}
|
||||
|
||||
func (f *Text) write(v interface{}) {
|
||||
_, _ = fmt.Fprint(f.writer, v)
|
||||
}
|
||||
|
||||
func (f *Text) newLine() {
|
||||
_, _ = fmt.Fprintln(f.writer)
|
||||
}
|
||||
|
||||
func getFieldNames(value reflect.Value) []string {
|
||||
var names []string
|
||||
valueType := value.Type()
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
names = append(names, valueType.Field(i).Name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func (f *Text) writeCompoundFields(value reflect.Value) {
|
||||
fieldNames := getFieldNames(value)
|
||||
for _, fieldName := range fieldNames {
|
||||
field := value.FieldByName(fieldName)
|
||||
if !isSimpleType(field) {
|
||||
f.writeValue(field)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package formatter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Yaml struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (f *Yaml) write(bytes []byte) {
|
||||
_, _ = fmt.Fprint(f.writer, string(bytes))
|
||||
}
|
||||
|
||||
func (f *Yaml) newLine() {
|
||||
_, _ = fmt.Fprintln(f.writer)
|
||||
}
|
||||
|
||||
func (f *Yaml) Write(o interface{}) {
|
||||
var b bytes.Buffer
|
||||
|
||||
yamlEncoder := yaml.NewEncoder(&b)
|
||||
yamlEncoder.SetIndent(2)
|
||||
_ = yamlEncoder.Encode(o)
|
||||
|
||||
f.write(b.Bytes())
|
||||
f.newLine()
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultErrorExitCode = 1
|
||||
)
|
||||
|
||||
var fatalErrHandler = fatal
|
||||
var PromptManualExitSignal = fmt.Errorf("prompt")
|
||||
|
||||
func fatal(msg string, code int) {
|
||||
if len(msg) > 0 {
|
||||
// add newline if needed
|
||||
if !strings.HasSuffix(msg, "\n") {
|
||||
msg += "\n"
|
||||
}
|
||||
fmt.Fprint(os.Stderr, msg)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
var ErrExit = fmt.Errorf("exit")
|
||||
|
||||
func CheckErr(err error) {
|
||||
checkErr(err, fatalErrHandler)
|
||||
}
|
||||
|
||||
func checkErr(err error, handleErr func(string, int)) {
|
||||
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case err == ErrExit:
|
||||
handleErr("", DefaultErrorExitCode)
|
||||
case err == PromptManualExitSignal:
|
||||
handleErr("Prompt exit", DefaultErrorExitCode)
|
||||
default:
|
||||
msg, ok := StandardErrorMessage(err)
|
||||
if !ok {
|
||||
msg = err.Error()
|
||||
if !strings.HasPrefix(msg, "error: ") {
|
||||
msg = fmt.Sprintf("error: %s", msg)
|
||||
}
|
||||
}
|
||||
handleErr(msg, DefaultErrorExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func StandardErrorMessage(err error) (string, bool) {
|
||||
switch t := err.(type) {
|
||||
case *url.Error:
|
||||
switch {
|
||||
case strings.Contains(t.Err.Error(), "connection refused"):
|
||||
host := t.URL
|
||||
if server, err := url.Parse(t.URL); err == nil {
|
||||
host = server.Host
|
||||
}
|
||||
return fmt.Sprintf("The connection to the server %s was refused - did you specify the right host or port?", host), true
|
||||
}
|
||||
return fmt.Sprintf("Unable to connect to the server: %v", t.Err), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func HandlePromptError(err error) error {
|
||||
if errors.Is(err, promptui.ErrInterrupt) || errors.Is(err, promptui.ErrAbort) {
|
||||
return PromptManualExitSignal
|
||||
}
|
||||
return fmt.Errorf("Prompt failed %v\n", err)
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package prompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
|
||||
"github.com/Bio-OS/bioos/internal/bioctl/utils"
|
||||
)
|
||||
|
||||
type PromptOptions struct {
|
||||
message string
|
||||
}
|
||||
|
||||
type PromptOption func(o *PromptOptions)
|
||||
|
||||
func WithInputMessage(message string) PromptOption {
|
||||
return func(o *PromptOptions) {
|
||||
o.message = message
|
||||
}
|
||||
}
|
||||
|
||||
func PromptRequiredString(name string, opts ...PromptOption) (string, error) {
|
||||
return PromptStringWithValidator(name, survey.Required, opts...)
|
||||
}
|
||||
|
||||
func PromptOptionalString(name string, opts ...PromptOption) (string, error) {
|
||||
return PromptStringWithValidator(name, OptionalInput, opts...)
|
||||
}
|
||||
|
||||
func PromptStringWithValidator(name string, validator survey.Validator, opts ...PromptOption) (string, error) {
|
||||
res := ""
|
||||
msg := name
|
||||
options := &PromptOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
if options.message != "" {
|
||||
msg = fmt.Sprintf("%s (%s)", name, options.message)
|
||||
}
|
||||
prompt := &survey.Input{
|
||||
Message: msg,
|
||||
}
|
||||
if err := survey.AskOne(prompt, &res, survey.WithValidator(validator), survey.WithIcons(func(icons *survey.IconSet) {
|
||||
// you can set any icons
|
||||
icons.Question.Text = SELECTICON
|
||||
// for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format
|
||||
//icons.Question.Format = "yellow+hb"
|
||||
icons.Question.Format = "cyan"
|
||||
})); err != nil {
|
||||
fmt.Println(err)
|
||||
return "", utils.HandlePromptError(err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// PromptStringSlice prompt for string slice
|
||||
func PromptStringSlice(label string) ([]string, error) {
|
||||
res := make([]string, 0)
|
||||
|
||||
var result string
|
||||
var err error
|
||||
for result != "quit" {
|
||||
result, err = PromptRequiredString(fmt.Sprintf("%s ('quit' to exit)", label))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.ToLower(result) != "quit" {
|
||||
res = append(res, result)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func PromptRequiredInt32(name string, opts ...PromptOption) (int32, error) {
|
||||
var res int32 = 0
|
||||
msg := name
|
||||
options := &PromptOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
if options.message != "" {
|
||||
msg = options.message
|
||||
}
|
||||
prompt := &survey.Input{
|
||||
Message: msg,
|
||||
}
|
||||
if err := survey.AskOne(prompt, &res, survey.WithValidator(ValidateIntegerNumberInput), survey.WithIcons(func(icons *survey.IconSet) {
|
||||
// you can set any icons
|
||||
icons.Question.Text = SELECTICON
|
||||
// for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format
|
||||
//icons.Question.Format = "yellow+hb"
|
||||
icons.Question.Format = "cyan"
|
||||
})); err != nil {
|
||||
return 0, utils.HandlePromptError(err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue