mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 20:26:13 +01:00
Merge branch 'master' into inline-css
This commit is contained in:
commit
bb15f67316
628
CONTRIBUTING.md
628
CONTRIBUTING.md
@ -1,308 +1,320 @@
|
||||
# Contributing to Harbor
|
||||
|
||||
## Welcome
|
||||
|
||||
Welcome to Harbor! This guide provides information on filing issues and guidelines for open source contributors. **Please leave comments / suggestions if you find something is missing or incorrect.**
|
||||
|
||||
Contributors are encouraged to collaborate using the following resources in addition to the GitHub [issue tacker](https://github.com/goharbor/harbor/issues):
|
||||
|
||||
**Twitter:** [@project_harbor](https://twitter.com/project_harbor)
|
||||
**User Group:** Join Harbor user email group: [harbor-users@googlegroups.com](https://groups.google.com/forum/#!forum/harbor-users) to get update of Harbor's news, features, releases, or to provide suggestion and feedback. To subscribe, send an email to [harbor-users+subscribe@googlegroups.com](mailto:harbor-users+subscribe@googlegroups.com) .
|
||||
**Developer Group:** Join Harbor developer group: [harbor-dev@googlegroups.com](https://groups.google.com/forum/#!forum/harbor-dev) for discussion on Harbor development and contribution. To subscribe, send an email to [harbor-dev+subscribe@googlegroups.com](mailto:harbor-dev+subscribe@googlegroups.com).
|
||||
**Slack:** Join Harbor's community for discussion and ask questions: [Cloud Native Computing Foundation](https://slack.cncf.io/), channel: #harbor and #harbor-dev
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Fork Repository
|
||||
|
||||
Fork the Harbor repository on GitHub to your personal account.
|
||||
```
|
||||
#Set golang environment
|
||||
export GOPATH=$HOME/go
|
||||
mkdir -p $GOPATH/src/github.com/goharbor
|
||||
|
||||
#Get code
|
||||
go get github.com/goharbor/harbor
|
||||
cd $GOPATH/src/github.com/goharbor/harbor
|
||||
|
||||
#Track repository under your personal account
|
||||
git config push.default nothing # Anything to avoid pushing to vmware/harbor by default
|
||||
git remote rename origin vmware
|
||||
git remote add $USER git@github.com:$USER/harbor.git
|
||||
git fetch $USER
|
||||
|
||||
```
|
||||
**NOTES:** Note that GOPATH can be any directory, the example above uses $HOME/go. Change $USER above to your own GitHub username.
|
||||
|
||||
To build the project, please refer the [build](docs/compile_guide.md) guideline.
|
||||
|
||||
### Repository Structure
|
||||
|
||||
Here is the basic structure of the harbor code base. Some of the key folders / files are commented for your references.
|
||||
```
|
||||
.
|
||||
...
|
||||
├── Makefile #Make file for compile and build code
|
||||
├── contrib #Contain documents, scripts, and other helpful things which are contributed by the community
|
||||
├── docs #Keep documents here
|
||||
├── make #Resource for building and setting up Harbor environment
|
||||
...
|
||||
├── src #Source code folder
|
||||
├── tests #Test cases for API / e2e testings
|
||||
└── tools #Keep supporting tools
|
||||
...
|
||||
```
|
||||
|
||||
The folder graph below shows the structure of the source code folder `harbor/src`, which will be your primary working directory. The key folders are also commented.
|
||||
```
|
||||
.
|
||||
├── adminserver # Source code for the admin server component
|
||||
│ ├── api
|
||||
│ ├── auth
|
||||
│ ├── client
|
||||
│ ├── handlers
|
||||
│ ├── systemcfg
|
||||
│ └── systeminfo
|
||||
├── common # Source code for some general components like dao etc.
|
||||
│ ├── api
|
||||
│ ├── config
|
||||
│ ├── dao
|
||||
│ ├── models
|
||||
│ ├── notifier
|
||||
│ ├── scheduler
|
||||
│ ├── secret
|
||||
│ ├── security
|
||||
│ └── utils
|
||||
├── jobservice # Source code for the job service component
|
||||
│ ├── api
|
||||
│ ├── config
|
||||
│ ├── job
|
||||
│ ├── replication
|
||||
│ ├── scan
|
||||
│ └── utils
|
||||
├── ui # Source code for the harbor service component
|
||||
│ ├── api
|
||||
│ ├── auth
|
||||
│ ├── config
|
||||
│ ├── controllers
|
||||
│ ├── filter
|
||||
│ ├── promgr
|
||||
│ ├── proxy
|
||||
│ ├── service
|
||||
│ ├── static
|
||||
│ ├── utils
|
||||
│ └── views
|
||||
├── portal # The code of harbor web UI
|
||||
│ ├── e2e
|
||||
│ ├── lib # Source code of @harbor/ui npm library which includes the main UI components of web UI
|
||||
│ └── src # General web page UI code of Harbor
|
||||
└── vendor # Go code dependencies
|
||||
├── github.com
|
||||
├── golang.org
|
||||
├── google.golang.org
|
||||
└── gopkg.in
|
||||
```
|
||||
|
||||
### Setup Development Environment
|
||||
|
||||
#### Go
|
||||
Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbor backend service development environment, please [set one up](https://golang.org/doc/install).
|
||||
|
||||
| Harbor | Requires Go |
|
||||
|----------|---------------|
|
||||
| 1.1 | 1.7.3 |
|
||||
| 1.2 | 1.7.3 |
|
||||
| 1.3 | 1.9.2 |
|
||||
| 1.4 | 1.9.2 |
|
||||
|
||||
Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions.
|
||||
|
||||
**Dependency Management:** Harbor uses [dep](https://github.com/golang/dep) for dependency management of go code. The official maintainers will take the responsibility for managing the code in `vendor` directory. Please don't try to submit a PR to update the dependency code, open an issue instead. If your PR requires a change in the vendor code please make sure you discuss it with the maintainers in advance.
|
||||
|
||||
#### Web
|
||||
|
||||
Harbor web UI is built based on [Clarity](https://vmware.github.io/clarity/) and [Angular](https://angular.io/) web framework. To setup web UI development environment, please make sure the [npm](https://www.npmjs.com/get-npm) tool is installed first.
|
||||
|
||||
| Harbor | Requires Angular | Requires Clarity |
|
||||
|----------|--------------------|--------------------|
|
||||
| 1.1 | 2.4.1 | 0.8.7 |
|
||||
| 1.2 | 4.1.3 | 0.9.8 |
|
||||
| 1.3 | 4.3.0 | 0.10.17 |
|
||||
| 1.4 | | |
|
||||
|
||||
**Npm Package Dependency:** Run the following commands to restore the package dependencies.
|
||||
```
|
||||
#For the web UI
|
||||
cd $REPO_DIR/src/portal
|
||||
npm install
|
||||
|
||||
#For the UI library
|
||||
cd $REPO_DIR/src/portal/lib
|
||||
npm install
|
||||
```
|
||||
|
||||
|
||||
To run the code, please refer to the [build](docs/compile_guide.md) guideline.
|
||||
|
||||
## Contribute Workflow
|
||||
|
||||
PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it.
|
||||
|
||||
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
|
||||
|
||||
The graphic shown below describes the overall workflow about how to contribute code to Harbor repository.
|
||||
![contribute workflow](docs/img/workflow.png)
|
||||
|
||||
### Fork and clone
|
||||
|
||||
Fork the Harbor repository and clone the code to your local workspace. Per Go's [workspace instructions](https://golang.org/doc/code.html#Workspaces), place Harbor's code on your `GOPATH`. Refer to section [Fork Repository](#fork-repository) for details.
|
||||
|
||||
Define a local working directory:
|
||||
```
|
||||
working_dir=$GOPATH/src/github.com/goharbor
|
||||
```
|
||||
|
||||
Set user to match your github profile name:
|
||||
```
|
||||
user={your github profile name}
|
||||
```
|
||||
|
||||
Both `$working_dir` and `$user` are mentioned in the figure above.
|
||||
|
||||
### Branch
|
||||
Changes should be made on your own fork in a new branch. The branch should be named `XXX-description` where XXX is the number of the issue. PR should be rebased on top of master without multiple branches mixed into the PR. If your PR do not merge cleanly, use commands listed below to get it up to date.
|
||||
|
||||
```
|
||||
#vmware is the origin upstream
|
||||
|
||||
cd $working_dir/kubernetes
|
||||
git fetch vmware
|
||||
git checkout master
|
||||
git rebase vmware/master
|
||||
```
|
||||
|
||||
Branch from the updated `master` branch:
|
||||
|
||||
```
|
||||
git checkout -b my_feature master
|
||||
```
|
||||
|
||||
### Develop, Build and Test
|
||||
|
||||
Write code on the new branch in your fork. The coding style used in Harbor is suggested by the Golang community. See the [style doc](https://github.com/golang/go/wiki/CodeReviewComments) for details.
|
||||
|
||||
Try to limit column width to 120 characters for both code and markdown documents such as this one.
|
||||
|
||||
Always run [golint](https://github.com/golang/lint) on source code before
|
||||
committing your changes.
|
||||
```
|
||||
#Install fgt and golint
|
||||
|
||||
go get -u golang.org/x/lint/golint
|
||||
go get github.com/GeertJohan/fgt
|
||||
|
||||
#In the #working_dir/harbor, run
|
||||
|
||||
go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
|
||||
|
||||
```
|
||||
|
||||
Unit test cases should be added to cover the new code. Unit test framework for backend services is using [go testing](https://golang.org/doc/code.html#Testing). The UI library test framework is built based on [Jasmine](http://jasmine.github.io/2.4/introduction.html) and [Karma](https://karma-runner.github.io/1.0/index.html), please refer to [Angular Testing](https://angular.io/guide/testing) for more details.
|
||||
|
||||
Run go test cases:
|
||||
```
|
||||
#cd #working_dir/src/[package]
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
Run UI library test cases:
|
||||
```
|
||||
#cd #working_dir/src/portal/lib
|
||||
npm run test
|
||||
```
|
||||
|
||||
To build code, please refer to [build](docs/compile_guide.md) guideline.
|
||||
|
||||
### Keep sync with upstream
|
||||
|
||||
Once your branch gets out of sync with the vmware/master branch, use the following commands to update:
|
||||
```
|
||||
git checkout my_feature
|
||||
git fetch -a
|
||||
git rebase vmware/master
|
||||
|
||||
```
|
||||
|
||||
Please don't use `git pull` instead of the above `fetch / rebase`. `git pull` does a merge, which leaves merge commits. These make the commit history messy and violate the principle that commits ought to be individually understandable and useful (see below). You can also consider changing your `.git/config` file via git config `branch.autoSetupRebase` always to change the behavior of `git pull`.
|
||||
|
||||
### Commit
|
||||
|
||||
Commit your changes if they're ready:
|
||||
```
|
||||
#git add -A
|
||||
git commit #-a
|
||||
git push --force-with-lease $user my_feature
|
||||
```
|
||||
|
||||
The commit message should follow the convention on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). Be sure to include any related GitHub issue references in the commit message. See [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues and commits.
|
||||
|
||||
To help write conforming commit messages, it is recommended to set up the [git-good-commit](https://github.com/tommarshall/git-good-commit) commit hook. Run this command in the Harbor repo's root directory:
|
||||
|
||||
```
|
||||
curl https://cdn.rawgit.com/tommarshall/git-good-commit/v0.6.1/hook.sh > .git/hooks/commit-msg && chmod +x .git/hooks/commit-msg
|
||||
```
|
||||
|
||||
### Automated Testing
|
||||
Once your pull request has been opened, harbor will run two CI pipelines against it.
|
||||
1. In the travis CI, your source code will be checked via `golint`, `go vet` and `go race` that makes sure the code is readable, safe and correct. Also all of unit tests will be triggered via `go test` against the pull request. What you need to pay attention to is the travis result and the coverage report.
|
||||
* If any failure in travis, you need to figure out whether it is introduced by your commits.
|
||||
* If the coverage dramatic decline, you need to commit unit test to coverage your code.
|
||||
2. In the drone CI, the E2E test will be triggered against the pull request. The pipeline is about to build and install harbor from source code, then to run four very basic E2E tests to validate the basic functionalities of harbor, like:
|
||||
* Registry Basic Verification, to validate the image can be pulled and pushed successful.
|
||||
* Clair Basic Verification, to validate the image can be scanned successful.
|
||||
* Notary Basic Verification, to validate the image can be signed successful.
|
||||
* Ldap Basic Verification, to validate harbor can work in LDAP environment.
|
||||
|
||||
### Push and Create PR
|
||||
When ready for review, push your branch to your fork repository on `github.com`:
|
||||
```
|
||||
git push --force-with-lease $user my_feature
|
||||
|
||||
```
|
||||
|
||||
Then visit your fork at https://github.com/$user/harbor and click the `Compare & Pull Request` button next to your `my_feature` branch to create a new pull request (PR). Description of a pull request should refer to all the issues that it addresses. Remember to put a reference to issues (such as `Closes #XXX` and `Fixes #XXX`) in commits so that the issues can be closed when the PR is merged.
|
||||
|
||||
Once your pull request has been opened it will be assigned to one or more reviewers. Those reviewers will do a thorough code review, looking for correctness, bugs, opportunities for improvement, documentation and comments, and style.
|
||||
|
||||
Commit changes made in response to review comments to the same branch on your fork.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
It is a great way to contribute to Harbor by reporting an issue. Well-written and complete bug reports are always welcome! Please open an issue on Github and follow the template to fill in required information.
|
||||
|
||||
Before opening any issue, please look up the existing [issues](https://github.com/vmware/harbor/issues) to avoid submitting a duplication.
|
||||
If you find a match, you can "subscribe" to it to get notified on updates. If you have additional helpful information about the issue, please leave a comment.
|
||||
|
||||
When reporting issues, always include:
|
||||
|
||||
* Version of docker engine and docker-compose
|
||||
* Configuration files of Harbor
|
||||
* Log files in /var/log/harbor/
|
||||
|
||||
Because the issues are open to the public, when submitting the log and configuration files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
|
||||
replace those parts with "REDACTED" or other strings like "****".
|
||||
|
||||
Be sure to include the steps to reproduce the problem if applicable. It can help us understand and fix your issue faster.
|
||||
|
||||
## Documenting
|
||||
|
||||
Update the documentation if you are creating or changing features. Good documentation is as important as the code itself.
|
||||
|
||||
The main location for the document is the `docs/` folder. The images referred in documents can be placed in `docs/img`.
|
||||
|
||||
Documents are written with Markdown text. See [Writing on GitHub](https://help.github.com/categories/writing-on-github/) for more details.
|
||||
|
||||
## Design new features
|
||||
|
||||
You can propose new designs for existing Harbor features. You can also design
|
||||
entirely new features. Please do open an issue on Github for discussion first. This is necessary to ensure the overall architecture is consistent and to avoid duplicated work in the roadmap.
|
||||
# Contributing to Harbor
|
||||
|
||||
## Welcome
|
||||
|
||||
Welcome to Harbor! This guide provides information on filing issues and guidelines for open source contributors. **Please leave comments / suggestions if you find something is missing or incorrect.**
|
||||
|
||||
Contributors are encouraged to collaborate using the following resources in addition to the GitHub [issue tacker](https://github.com/goharbor/harbor/issues):
|
||||
|
||||
**Twitter:** [@project_harbor](https://twitter.com/project_harbor)
|
||||
**User Group:** Join Harbor user email group: [harbor-users@googlegroups.com](https://groups.google.com/forum/#!forum/harbor-users) to get update of Harbor's news, features, releases, or to provide suggestion and feedback. To subscribe, send an email to [harbor-users+subscribe@googlegroups.com](mailto:harbor-users+subscribe@googlegroups.com) .
|
||||
**Developer Group:** Join Harbor developer group: [harbor-dev@googlegroups.com](https://groups.google.com/forum/#!forum/harbor-dev) for discussion on Harbor development and contribution. To subscribe, send an email to [harbor-dev+subscribe@googlegroups.com](mailto:harbor-dev+subscribe@googlegroups.com).
|
||||
**Slack:** Join Harbor's community for discussion and ask questions: [Cloud Native Computing Foundation](https://slack.cncf.io/), channel: #harbor and #harbor-dev
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Fork Repository
|
||||
|
||||
Fork the Harbor repository on GitHub to your personal account.
|
||||
```
|
||||
#Set golang environment
|
||||
export GOPATH=$HOME/go
|
||||
mkdir -p $GOPATH/src/github.com/goharbor
|
||||
|
||||
#Get code
|
||||
go get github.com/goharbor/harbor
|
||||
cd $GOPATH/src/github.com/goharbor/harbor
|
||||
|
||||
#Track repository under your personal account
|
||||
git config push.default nothing # Anything to avoid pushing to goharbor/harbor by default
|
||||
git remote rename origin goharbor
|
||||
git remote add $USER git@github.com:$USER/harbor.git
|
||||
git fetch $USER
|
||||
|
||||
```
|
||||
**NOTES:** Note that GOPATH can be any directory, the example above uses $HOME/go. Change $USER above to your own GitHub username.
|
||||
|
||||
To build the project, please refer the [build](docs/compile_guide.md) guideline.
|
||||
|
||||
### Repository Structure
|
||||
|
||||
Here is the basic structure of the harbor code base. Some of the key folders / files are commented for your references.
|
||||
```
|
||||
.
|
||||
...
|
||||
├── Makefile # Make file for compile and build code
|
||||
├── contrib # Contain documents, scripts, and other helpful things which are contributed by the community
|
||||
├── docs # Keep documents here
|
||||
├── make # Resource for building and setting up Harbor environment
|
||||
...
|
||||
├── src # Source code folder
|
||||
├── tests # Test cases for API / e2e testings
|
||||
└── tools # Keep supporting tools
|
||||
...
|
||||
```
|
||||
|
||||
The folder graph below shows the structure of the source code folder `harbor/src`, which will be your primary working directory. The key folders are also commented.
|
||||
```
|
||||
.
|
||||
├── adminserver # Source code for the admin server component
|
||||
│ ├── api
|
||||
│ ├── auth
|
||||
│ ├── client
|
||||
│ ├── handlers
|
||||
│ ├── systemcfg
|
||||
│ └── systeminfo
|
||||
├── common # Source code for some general components like dao etc.
|
||||
│ ├── api
|
||||
│ ├── config
|
||||
│ ├── dao
|
||||
│ ├── models
|
||||
│ ├── notifier
|
||||
│ ├── scheduler
|
||||
│ ├── secret
|
||||
│ ├── security
|
||||
│ └── utils
|
||||
├── jobservice # Source code for the job service component
|
||||
│ ├── api
|
||||
│ ├── config
|
||||
│ ├── job
|
||||
│ ├── replication
|
||||
│ ├── scan
|
||||
│ └── utils
|
||||
├── ui # Source code for the harbor service component
|
||||
│ ├── api
|
||||
│ ├── auth
|
||||
│ ├── config
|
||||
│ ├── controllers
|
||||
│ ├── filter
|
||||
│ ├── promgr
|
||||
│ ├── proxy
|
||||
│ ├── service
|
||||
│ ├── static
|
||||
│ ├── utils
|
||||
│ └── views
|
||||
├── portal # The code of harbor web UI
|
||||
│ ├── e2e
|
||||
│ ├── lib # Source code of @harbor/ui npm library which includes the main UI components of web UI
|
||||
│ └── src # General web page UI code of Harbor
|
||||
└── vendor # Go code dependencies
|
||||
├── github.com
|
||||
├── golang.org
|
||||
├── google.golang.org
|
||||
└── gopkg.in
|
||||
```
|
||||
|
||||
### Setup Development Environment
|
||||
|
||||
#### Go
|
||||
Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbor backend service development environment, please [set one up](https://golang.org/doc/install).
|
||||
|
||||
| Harbor | Requires Go |
|
||||
|----------|---------------|
|
||||
| 1.1 | 1.7.3 |
|
||||
| 1.2 | 1.7.3 |
|
||||
| 1.3 | 1.9.2 |
|
||||
| 1.4 | 1.9.2 |
|
||||
| 1.5 | 1.9.2 |
|
||||
| 1.6 | 1.9.2 |
|
||||
| 1.6 | tbd |
|
||||
|
||||
Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions.
|
||||
|
||||
**Dependency Management:** Harbor uses [dep](https://github.com/golang/dep) for dependency management of go code. The official maintainers will take the responsibility for managing the code in `vendor` directory. Please don't try to submit a PR to update the dependency code, open an issue instead. If your PR requires a change in the vendor code please make sure you discuss it with the maintainers in advance.
|
||||
|
||||
#### Web
|
||||
|
||||
Harbor web UI is built based on [Clarity](https://vmware.github.io/clarity/) and [Angular](https://angular.io/) web framework. To setup web UI development environment, please make sure the [npm](https://www.npmjs.com/get-npm) tool is installed first.
|
||||
|
||||
| Harbor | Requires Angular | Requires Clarity |
|
||||
|----------|--------------------|--------------------|
|
||||
| 1.1 | 2.4.1 | 0.8.7 |
|
||||
| 1.2 | 4.1.3 | 0.9.8 |
|
||||
| 1.3 | 4.3.0 | 0.10.17 |
|
||||
| 1.4 | 4.3.0 | 0.10.17 |
|
||||
| 1.5 | 4.3.0 | 0.10.27 |
|
||||
| 1.6 | 4.3.0 | 0.10.27 |
|
||||
|
||||
**npm Package Dependency:** Run the following commands to restore the package dependencies.
|
||||
```
|
||||
#For the web UI
|
||||
cd $REPO_DIR/src/portal
|
||||
npm install
|
||||
|
||||
#For the UI library
|
||||
cd $REPO_DIR/src/portal/lib
|
||||
npm install
|
||||
```
|
||||
|
||||
|
||||
To run the code, please refer to the [build](docs/compile_guide.md) guideline.
|
||||
|
||||
## Contribute Workflow
|
||||
|
||||
PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it.
|
||||
|
||||
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
|
||||
|
||||
The graphic shown below describes the overall workflow about how to contribute code to Harbor repository.
|
||||
![contribute workflow](docs/img/workflow.png)
|
||||
|
||||
### Fork and clone
|
||||
|
||||
Fork the Harbor repository and clone the code to your local workspace. Per Go's [workspace instructions](https://golang.org/doc/code.html#Workspaces), place Harbor's code on your `GOPATH`. Refer to section [Fork Repository](#fork-repository) for details.
|
||||
|
||||
Define a local working directory:
|
||||
```
|
||||
working_dir=$GOPATH/src/github.com/goharbor
|
||||
```
|
||||
|
||||
Set user to match your github profile name:
|
||||
```
|
||||
user={your github profile name}
|
||||
```
|
||||
|
||||
Both `$working_dir` and `$user` are mentioned in the figure above.
|
||||
|
||||
### Branch
|
||||
Changes should be made on your own fork in a new branch. The branch should be named `XXX-description` where XXX is the number of the issue. PR should be rebased on top of master without multiple branches mixed into the PR. If your PR do not merge cleanly, use commands listed below to get it up to date.
|
||||
|
||||
```
|
||||
#goharbor is the origin upstream
|
||||
|
||||
cd $working_dir/kubernetes
|
||||
git fetch goharbor
|
||||
git checkout master
|
||||
git rebase goharbor/master
|
||||
```
|
||||
|
||||
Branch from the updated `master` branch:
|
||||
|
||||
```
|
||||
git checkout -b my_feature master
|
||||
```
|
||||
|
||||
### Develop, Build and Test
|
||||
|
||||
Write code on the new branch in your fork. The coding style used in Harbor is suggested by the Golang community. See the [style doc](https://github.com/golang/go/wiki/CodeReviewComments) for details.
|
||||
|
||||
Try to limit column width to 120 characters for both code and markdown documents such as this one.
|
||||
|
||||
Always run [golint](https://github.com/golang/lint) on source code before
|
||||
committing your changes.
|
||||
```
|
||||
#Install fgt and golint
|
||||
|
||||
go get -u golang.org/x/lint/golint
|
||||
go get github.com/GeertJohan/fgt
|
||||
|
||||
#In the #working_dir/harbor, run
|
||||
|
||||
go list ./... | grep -v -E 'vendor|tests' | xargs -L1 fgt golint
|
||||
|
||||
```
|
||||
|
||||
Unit test cases should be added to cover the new code. Unit test framework for backend services is using [go testing](https://golang.org/doc/code.html#Testing). The UI library test framework is built based on [Jasmine](http://jasmine.github.io/2.4/introduction.html) and [Karma](https://karma-runner.github.io/1.0/index.html), please refer to [Angular Testing](https://angular.io/guide/testing) for more details.
|
||||
|
||||
Run go test cases:
|
||||
```
|
||||
#cd #working_dir/src/[package]
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
Run UI library test cases:
|
||||
```
|
||||
#cd #working_dir/src/portal/lib
|
||||
npm run test
|
||||
```
|
||||
|
||||
To build code, please refer to [build](docs/compile_guide.md) guideline.
|
||||
|
||||
### Keep sync with upstream
|
||||
|
||||
```
|
||||
Once your branch gets out of sync with the goharbor/master branch, use the following commands to update:
|
||||
```
|
||||
git checkout my_feature
|
||||
git fetch -a
|
||||
git rebase goharbor/master
|
||||
|
||||
```
|
||||
|
||||
Please don't use `git pull` instead of the above `fetch / rebase`. `git pull` does a merge, which leaves merge commits. These make the commit history messy and violate the principle that commits ought to be individually understandable and useful (see below). You can also consider changing your `.git/config` file via git config `branch.autoSetupRebase` always to change the behavior of `git pull`.
|
||||
|
||||
### Commit
|
||||
|
||||
As Harbor has integrated the [DCO (Developer Certificate of Origin)](https://probot.github.io/apps/dco/) check tool, contributors are required to sign-off that they adhere to those requirements by adding a `Signed-off-by` line to the commit messages. Git has even provided a `-s` command line option to append that automatically to your commit messages, please use it when you commit your changes.
|
||||
|
||||
```bash
|
||||
$ git commit -s -m 'This is my commit message'
|
||||
```
|
||||
|
||||
Commit your changes if they're ready:
|
||||
```
|
||||
#git add -A
|
||||
git commit -s #-a
|
||||
git push --force-with-lease $user my_feature
|
||||
```
|
||||
|
||||
The commit message should follow the convention on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). Be sure to include any related GitHub issue references in the commit message. See [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues and commits.
|
||||
|
||||
To help write conforming commit messages, it is recommended to set up the [git-good-commit](https://github.com/tommarshall/git-good-commit) commit hook. Run this command in the Harbor repo's root directory:
|
||||
|
||||
```
|
||||
curl https://cdn.rawgit.com/tommarshall/git-good-commit/v0.6.1/hook.sh > .git/hooks/commit-msg && chmod +x .git/hooks/commit-msg
|
||||
```
|
||||
|
||||
### Automated Testing
|
||||
Once your pull request has been opened, harbor will run two CI pipelines against it.
|
||||
1. In the travis CI, your source code will be checked via `golint`, `go vet` and `go race` that makes sure the code is readable, safe and correct. Also all of unit tests will be triggered via `go test` against the pull request. What you need to pay attention to is the travis result and the coverage report.
|
||||
* If any failure in travis, you need to figure out whether it is introduced by your commits.
|
||||
* If the coverage dramatic decline, you need to commit unit test to coverage your code.
|
||||
2. In the drone CI, the E2E test will be triggered against the pull request. The pipeline is about to build and install harbor from source code, then to run four very basic E2E tests to validate the basic functionalities of harbor, like:
|
||||
* Registry Basic Verification, to validate the image can be pulled and pushed successful.
|
||||
* Clair Basic Verification, to validate the image can be scanned successful.
|
||||
* Notary Basic Verification, to validate the image can be signed successful.
|
||||
* Ldap Basic Verification, to validate harbor can work in LDAP environment.
|
||||
|
||||
### Push and Create PR
|
||||
When ready for review, push your branch to your fork repository on `github.com`:
|
||||
```
|
||||
git push --force-with-lease $user my_feature
|
||||
|
||||
```
|
||||
|
||||
Then visit your fork at https://github.com/$user/harbor and click the `Compare & Pull Request` button next to your `my_feature` branch to create a new pull request (PR). Description of a pull request should refer to all the issues that it addresses. Remember to put a reference to issues (such as `Closes #XXX` and `Fixes #XXX`) in commits so that the issues can be closed when the PR is merged.
|
||||
|
||||
Once your pull request has been opened it will be assigned to one or more reviewers. Those reviewers will do a thorough code review, looking for correctness, bugs, opportunities for improvement, documentation and comments, and style.
|
||||
|
||||
Commit changes made in response to review comments to the same branch on your fork.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
It is a great way to contribute to Harbor by reporting an issue. Well-written and complete bug reports are always welcome! Please open an issue on Github and follow the template to fill in required information.
|
||||
|
||||
Before opening any issue, please look up the existing [issues](https://github.com/goharbor/harbor/issues) to avoid submitting a duplication.
|
||||
If you find a match, you can "subscribe" to it to get notified on updates. If you have additional helpful information about the issue, please leave a comment.
|
||||
|
||||
When reporting issues, always include:
|
||||
|
||||
* Version of docker engine and docker-compose
|
||||
* Configuration files of Harbor
|
||||
* Log files in /var/log/harbor/
|
||||
|
||||
Because the issues are open to the public, when submitting the log and configuration files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
|
||||
replace those parts with "REDACTED" or other strings like "****".
|
||||
|
||||
Be sure to include the steps to reproduce the problem if applicable. It can help us understand and fix your issue faster.
|
||||
|
||||
## Documenting
|
||||
|
||||
Update the documentation if you are creating or changing features. Good documentation is as important as the code itself.
|
||||
|
||||
The main location for the document is the `docs/` folder. The images referred in documents can be placed in `docs/img`.
|
||||
|
||||
Documents are written with Markdown text. See [Writing on GitHub](https://help.github.com/categories/writing-on-github/) for more details.
|
||||
|
||||
## Design new features
|
||||
|
||||
You can propose new designs for existing Harbor features. You can also design
|
||||
entirely new features. Please do open an issue on Github for discussion first. This is necessary to ensure the overall architecture is consistent and to avoid duplicated work in the roadmap.
|
||||
|
@ -9,7 +9,7 @@ Harbor is deployed as several Docker containers and most of the code is written
|
||||
|
||||
Software | Required Version
|
||||
----------------------|--------------------------
|
||||
docker | 1.12.0 +
|
||||
docker | 17.05 +
|
||||
docker-compose | 1.11.0 +
|
||||
python | 2.7 +
|
||||
git | 1.9.1 +
|
||||
|
@ -3015,7 +3015,7 @@ paths:
|
||||
$ref: '#/definitions/InternalChartAPIError'
|
||||
'507':
|
||||
$ref: '#/definitions/InsufficientStorageChartAPIError'
|
||||
/chartrepo/:repo/charts/:name/:version/labels:
|
||||
/chartrepo/{repo}/charts/{name}/{version}/labels:
|
||||
get:
|
||||
summary: Return the attahced labels of chart.
|
||||
description: Return the attahced labels of the specified chart version.
|
||||
@ -3094,7 +3094,7 @@ paths:
|
||||
$ref: '#/definitions/ConflictFormatedError'
|
||||
'500':
|
||||
$ref: '#/definitions/InternalChartAPIError'
|
||||
/chartrepo/:repo/charts/:name/:version/labels/:id:
|
||||
/chartrepo/{repo}/charts/{name}/{version}/labels/{id}:
|
||||
delete:
|
||||
summary: Remove label from chart.
|
||||
description: Remove label from the specified chart version.
|
||||
@ -4513,4 +4513,4 @@ definitions:
|
||||
type: array
|
||||
description: A list of label
|
||||
items:
|
||||
$ref: '#/definitions/Label'
|
||||
$ref: '#/definitions/Label'
|
||||
|
@ -5,6 +5,7 @@ RUN mkdir -p /build_dir
|
||||
|
||||
COPY make/photon/portal/entrypoint.sh /
|
||||
COPY src/portal /portal_src
|
||||
COPY ./docs/swagger.yaml /portal_src
|
||||
|
||||
WORKDIR /portal_src
|
||||
|
||||
@ -24,7 +25,11 @@ RUN tdnf install -y nginx >> /dev/null \
|
||||
EXPOSE 80
|
||||
VOLUME /var/cache/nginx /var/log/nginx /run
|
||||
|
||||
|
||||
COPY --from=nodeportal /build_dir/dist /usr/share/nginx/html
|
||||
COPY --from=nodeportal /build_dir/swagger.yaml /usr/share/nginx/html
|
||||
COPY --from=nodeportal /build_dir/swagger.json /usr/share/nginx/html
|
||||
|
||||
COPY make/photon/portal/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
STOPSIGNAL SIGQUIT
|
||||
|
@ -5,6 +5,11 @@ cd /build_dir
|
||||
cp -r /portal_src/* .
|
||||
ls -la
|
||||
|
||||
# Update
|
||||
apt-get update
|
||||
apt-get install -y ruby
|
||||
ruby -ryaml -rjson -e 'puts JSON.pretty_generate(YAML.load(ARGF))' swagger.yaml>swagger.json
|
||||
|
||||
cat ./package.json
|
||||
npm install
|
||||
|
||||
@ -13,4 +18,4 @@ npm run build_lib
|
||||
npm run link_lib
|
||||
|
||||
## Build production
|
||||
npm run release
|
||||
npm run release
|
||||
|
47079
open_source_license
47079
open_source_license
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego/validation"
|
||||
http_error "github.com/goharbor/harbor/src/common/utils/error"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
@ -103,8 +103,8 @@ func (b *BaseAPI) ParseAndHandleError(text string, err error) {
|
||||
return
|
||||
}
|
||||
log.Errorf("%s: %v", text, err)
|
||||
if e, ok := err.(*http_error.HTTPError); ok {
|
||||
b.RenderError(e.StatusCode, e.Detail)
|
||||
if e, ok := err.(*commonhttp.Error); ok {
|
||||
b.RenderError(e.Code, e.Message)
|
||||
return
|
||||
}
|
||||
b.RenderError(http.StatusInternalServerError, "")
|
||||
|
83
src/common/config/encrypt/encrypt.go
Normal file
83
src/common/config/encrypt/encrypt.go
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 encrypt
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultKeyPath = "/etc/core/key"
|
||||
)
|
||||
|
||||
// Encryptor encrypts or decrypts a strings
|
||||
type Encryptor interface {
|
||||
// Encrypt encrypts plaintext
|
||||
Encrypt(string) (string, error)
|
||||
// Decrypt decrypts ciphertext
|
||||
Decrypt(string) (string, error)
|
||||
}
|
||||
|
||||
// AESEncryptor uses AES to encrypt or decrypt string
|
||||
type AESEncryptor struct {
|
||||
keyProvider KeyProvider
|
||||
keyParams map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAESEncryptor returns an instance of an AESEncryptor
|
||||
func NewAESEncryptor(keyProvider KeyProvider) Encryptor {
|
||||
return &AESEncryptor{
|
||||
keyProvider: keyProvider,
|
||||
}
|
||||
}
|
||||
|
||||
var encryptInstance Encryptor
|
||||
var encryptOnce sync.Once
|
||||
|
||||
// Instance ... Get instance of encryptor
|
||||
func Instance() Encryptor {
|
||||
encryptOnce.Do(func() {
|
||||
kp := os.Getenv("KEY_PATH")
|
||||
if len(kp) == 0 {
|
||||
kp = defaultKeyPath
|
||||
}
|
||||
log.Infof("the path of key used by key provider: %s", kp)
|
||||
encryptInstance = NewAESEncryptor(NewFileKeyProvider(kp))
|
||||
|
||||
})
|
||||
return encryptInstance
|
||||
}
|
||||
|
||||
// Encrypt ...
|
||||
func (a *AESEncryptor) Encrypt(plaintext string) (string, error) {
|
||||
key, err := a.keyProvider.Get(a.keyParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return utils.ReversibleEncrypt(plaintext, key)
|
||||
}
|
||||
|
||||
// Decrypt ...
|
||||
func (a *AESEncryptor) Decrypt(ciphertext string) (string, error) {
|
||||
key, err := a.keyProvider.Get(a.keyParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return utils.ReversibleDecrypt(ciphertext, key)
|
||||
}
|
39
src/common/config/encrypt/encrypt_test.go
Normal file
39
src/common/config/encrypt/encrypt_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
secret := []byte("9TXCcHgNAAp1aSHh")
|
||||
filename, err := ioutil.TempFile(os.TempDir(), "keyfile")
|
||||
err = ioutil.WriteFile(filename.Name(), secret, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create temp key file\n")
|
||||
}
|
||||
|
||||
defer os.Remove(filename.Name())
|
||||
|
||||
os.Setenv("KEY_PATH", filename.Name())
|
||||
|
||||
ret := m.Run()
|
||||
os.Exit(ret)
|
||||
}
|
||||
|
||||
func TestEncryptDecrypt(t *testing.T) {
|
||||
password := "zhu888jie"
|
||||
encrypted, err := Instance().Encrypt(password)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decrypt password, error %v", err)
|
||||
}
|
||||
decrypted, err := Instance().Decrypt(encrypted)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decrypt password, error %v", err)
|
||||
}
|
||||
assert.NotEqual(t, password, encrypted)
|
||||
assert.Equal(t, password, decrypted)
|
||||
}
|
48
src/common/config/encrypt/keyprovider.go
Normal file
48
src/common/config/encrypt/keyprovider.go
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 encrypt
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// KeyProvider provides the key used to encrypt and decrypt attrs
|
||||
type KeyProvider interface {
|
||||
// Get returns the key
|
||||
// params can be used to pass parameters in different implements
|
||||
Get(params map[string]interface{}) (string, error)
|
||||
}
|
||||
|
||||
// FileKeyProvider reads key from file
|
||||
type FileKeyProvider struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// NewFileKeyProvider returns an instance of FileKeyProvider
|
||||
// path: where the key should be read from
|
||||
func NewFileKeyProvider(path string) KeyProvider {
|
||||
return &FileKeyProvider{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the key read from file
|
||||
func (f *FileKeyProvider) Get(params map[string]interface{}) (string, error) {
|
||||
b, err := ioutil.ReadFile(f.path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
@ -11,20 +11,34 @@
|
||||
// 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 error
|
||||
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
err := &HTTPError{
|
||||
StatusCode: 404,
|
||||
Detail: "not found",
|
||||
func TestGetOfFileKeyProvider(t *testing.T) {
|
||||
path := "/tmp/key"
|
||||
key := "key_content"
|
||||
|
||||
if err := ioutil.WriteFile(path, []byte(key), 0777); err != nil {
|
||||
t.Errorf("failed to write to file %s: %v", path, err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(path)
|
||||
|
||||
provider := NewFileKeyProvider(path)
|
||||
k, err := provider.Get(nil)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get key from the file provider: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err.Error() != "404 not found" {
|
||||
t.Fatalf("unexpected content: %s != %s",
|
||||
err.Error(), "404 not found")
|
||||
if k != key {
|
||||
t.Errorf("unexpected key: %s != %s", k, key)
|
||||
return
|
||||
}
|
||||
}
|
70
src/common/config/metadata/metadata.go
Normal file
70
src/common/config/metadata/metadata.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var metaDataOnce sync.Once
|
||||
var metaDataInstance *CfgMetaData
|
||||
|
||||
// Instance - Get Instance, make it singleton because there is only one copy of metadata in an env
|
||||
func Instance() *CfgMetaData {
|
||||
metaDataOnce.Do(func() {
|
||||
metaDataInstance = newCfgMetaData()
|
||||
metaDataInstance.init()
|
||||
})
|
||||
return metaDataInstance
|
||||
}
|
||||
|
||||
func newCfgMetaData() *CfgMetaData {
|
||||
return &CfgMetaData{metaMap: make(map[string]Item)}
|
||||
}
|
||||
|
||||
// CfgMetaData ...
|
||||
type CfgMetaData struct {
|
||||
metaMap map[string]Item
|
||||
}
|
||||
|
||||
// init ...
|
||||
func (c *CfgMetaData) init() {
|
||||
c.initFromArray(ConfigList)
|
||||
}
|
||||
|
||||
// initFromArray - Initial metadata from an array
|
||||
func (c *CfgMetaData) initFromArray(items []Item) {
|
||||
c.metaMap = make(map[string]Item)
|
||||
for _, item := range items {
|
||||
c.metaMap[item.Name] = item
|
||||
}
|
||||
}
|
||||
|
||||
// GetByName - Get current metadata of current name, if not defined, return false in second params
|
||||
func (c *CfgMetaData) GetByName(name string) (*Item, bool) {
|
||||
if item, ok := c.metaMap[name]; ok {
|
||||
return &item, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetAll - Get all metadata in current env
|
||||
func (c *CfgMetaData) GetAll() []Item {
|
||||
metaDataList := make([]Item, 0)
|
||||
for _, value := range c.metaMap {
|
||||
metaDataList = append(metaDataList, value)
|
||||
}
|
||||
return metaDataList
|
||||
}
|
51
src/common/config/metadata/metadata_test.go
Normal file
51
src/common/config/metadata/metadata_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCfgMetaData_InitFromArray(t *testing.T) {
|
||||
testArray := []Item{
|
||||
{Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", Name: "admin_initial_password", ItemType: &PasswordType{}, Editable: true},
|
||||
{Scope: SystemScope, Group: BasicGroup, EnvKey: "ADMIRAL_URL", DefaultValue: "NA", Name: "admiral_url", ItemType: &StringType{}, Editable: false},
|
||||
{Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", Name: "auth_mode", ItemType: &StringType{}, Editable: false},
|
||||
{Scope: SystemScope, Group: BasicGroup, EnvKey: "CFG_EXPIRATION", DefaultValue: "5", Name: "cfg_expiration", ItemType: &StringType{}, Editable: false},
|
||||
{Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", Name: "chart_repository_url", ItemType: &StringType{}, Editable: false},
|
||||
}
|
||||
curInst := Instance()
|
||||
curInst.initFromArray(testArray)
|
||||
|
||||
if len(metaDataInstance.metaMap) != 5 {
|
||||
t.Errorf("Can not initial metadata, size %v", len(metaDataInstance.metaMap))
|
||||
}
|
||||
item, ok := curInst.GetByName("admin_initial_password")
|
||||
if ok == false {
|
||||
t.Errorf("Can not get admin_initial_password metadata")
|
||||
}
|
||||
if item.Name != "admin_initial_password" {
|
||||
t.Errorf("Can not get admin_initial_password metadata")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCfgMetaData_Init(t *testing.T) {
|
||||
curInst := Instance()
|
||||
curInst.init()
|
||||
if len(metaDataInstance.metaMap) < 60 {
|
||||
t.Errorf("Can not initial metadata, size %v", len(metaDataInstance.metaMap))
|
||||
}
|
||||
}
|
134
src/common/config/metadata/metadatalist.go
Normal file
134
src/common/config/metadata/metadatalist.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 metadata
|
||||
|
||||
import "github.com/goharbor/harbor/src/common"
|
||||
|
||||
// Item - Configure item include default value, type, env name
|
||||
type Item struct {
|
||||
// The Scope of this configuration item: eg: SystemScope, UserScope
|
||||
Scope string `json:"scope,omitempty"`
|
||||
// email, ldapbasic, ldapgroup, uaa settings, used to retieve configure items by group
|
||||
Group string `json:"group,omitempty"`
|
||||
// environment key to retrieves this value when initialize, for example: POSTGRESQL_HOST, only used for system settings, for user settings no EnvKey
|
||||
EnvKey string `json:"environment_key,omitempty"`
|
||||
// The default string value for this key
|
||||
DefaultValue string `json:"default_value,omitempty"`
|
||||
// The key for current configure settings in database or rest api
|
||||
Name string `json:"name,omitempty"`
|
||||
// It can be &IntType{}, &StringType{}, &BoolType{}, &PasswordType{}, &MapType{} etc, any type interface implementation
|
||||
ItemType Type
|
||||
// Is this settign can be modified after configure
|
||||
Editable bool `json:"editable,omitempty"`
|
||||
}
|
||||
|
||||
// Constant for configure item
|
||||
const (
|
||||
// Scope
|
||||
UserScope = "user"
|
||||
SystemScope = "system"
|
||||
// Group
|
||||
LdapBasicGroup = "ldapbasic"
|
||||
LdapGroupGroup = "ldapgroup"
|
||||
EmailGroup = "email"
|
||||
UAAGroup = "uaa"
|
||||
DatabaseGroup = "database"
|
||||
// Put all config items do not belong a existing group into basic
|
||||
BasicGroup = "basic"
|
||||
ClairGroup = "clair"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// ConfigList - All configure items used in harbor
|
||||
// Steps to onboard a new setting
|
||||
// 1. Add configure item in metadatalist.go
|
||||
// 2. Get/Set config settings by CfgManager
|
||||
// 3. CfgManager.Load()/CfgManager.Save() to load/save from configure storage.
|
||||
ConfigList = []Item{
|
||||
{Name: "admin_initial_password", Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", ItemType: &PasswordType{}, Editable: true},
|
||||
{Name: "admiral_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "ADMIRAL_URL", DefaultValue: "NA", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "auth_mode", Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "cfg_expiration", Scope: SystemScope, Group: BasicGroup, EnvKey: "CFG_EXPIRATION", DefaultValue: "5", ItemType: &IntType{}, Editable: false},
|
||||
{Name: "chart_repository_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", ItemType: &StringType{}, Editable: false},
|
||||
|
||||
{Name: "clair_db", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "clair_db_host", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_HOST", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "clair_db_password", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_PASSWORD", DefaultValue: "root123", ItemType: &PasswordType{}, Editable: false},
|
||||
{Name: "clair_db_port", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_PORT", DefaultValue: "5432", ItemType: &IntType{}, Editable: false},
|
||||
{Name: "clair_db_sslmode", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_SSLMODE", DefaultValue: "disable", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "clair_db_username", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_DB_USERNAME", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "clair_url", Scope: SystemScope, Group: ClairGroup, EnvKey: "CLAIR_URL", DefaultValue: "http://clair:6060", ItemType: &StringType{}, Editable: false},
|
||||
|
||||
{Name: "core_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "CORE_URL", DefaultValue: "http://core:8080", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "database_type", Scope: SystemScope, Group: BasicGroup, EnvKey: "DATABASE_TYPE", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false},
|
||||
|
||||
{Name: "email_from", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_FROM", DefaultValue: "admin <sample_admin@mydomain.com>", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "email_host", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_HOST", DefaultValue: "smtp.mydomain.com", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "email_identity", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_IDENTITY", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "email_insecure", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_INSECURE", DefaultValue: "false", ItemType: &BoolType{}, Editable: false},
|
||||
{Name: "email_password", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_PWD", DefaultValue: "", ItemType: &PasswordType{}, Editable: false},
|
||||
{Name: "email_port", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_PORT", DefaultValue: "25", ItemType: &IntType{}, Editable: false},
|
||||
{Name: "email_ssl", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_SSL", DefaultValue: "false", ItemType: &BoolType{}, Editable: false},
|
||||
{Name: "email_username", Scope: UserScope, Group: EmailGroup, EnvKey: "EMAIL_USR", DefaultValue: "sample_admin@mydomain.com", ItemType: &StringType{}, Editable: false},
|
||||
|
||||
{Name: "ext_endpoint", Scope: SystemScope, Group: BasicGroup, EnvKey: "EXT_ENDPOINT", DefaultValue: "https://host01.com", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "jobservice_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "JOBSERVICE_URL", DefaultValue: "http://jobservice:8080", ItemType: &StringType{}, Editable: false},
|
||||
|
||||
{Name: "ldap_base_dn", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_BASE_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "ldap_filter", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_FILTER", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "ldap_group_base_dn", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_BASE_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "ldap_group_admin_dn", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_ADMIN_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "ldap_group_attribute_name", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_GID", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "ldap_group_search_filter", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_FILTER", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "ldap_group_search_scope", Scope: UserScope, Group: LdapGroupGroup, EnvKey: "LDAP_GROUP_SCOPE", DefaultValue: "2", ItemType: &IntType{}, Editable: false},
|
||||
{Name: "ldap_scope", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SCOPE", DefaultValue: "2", ItemType: &IntType{}, Editable: true},
|
||||
{Name: "ldap_search_dn", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SEARCH_DN", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "ldap_search_password", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_SEARCH_PWD", DefaultValue: "", ItemType: &PasswordType{}, Editable: false},
|
||||
{Name: "ldap_timeout", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_TIMEOUT", DefaultValue: "5", ItemType: &IntType{}, Editable: false},
|
||||
{Name: "ldap_uid", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_UID", DefaultValue: "cn", ItemType: &StringType{}, Editable: true},
|
||||
{Name: "ldap_url", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_URL", DefaultValue: "", ItemType: &StringType{}, Editable: true},
|
||||
{Name: "ldap_verify_cert", Scope: UserScope, Group: LdapBasicGroup, EnvKey: "LDAP_VERIFY_CERT", DefaultValue: "true", ItemType: &BoolType{}, Editable: false},
|
||||
|
||||
{Name: "max_job_workers", Scope: SystemScope, Group: BasicGroup, EnvKey: "MAX_JOB_WORKERS", DefaultValue: "10", ItemType: &IntType{}, Editable: false},
|
||||
{Name: "notary_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "NOTARY_URL", DefaultValue: "http://notary-server:4443", ItemType: &StringType{}, Editable: false},
|
||||
|
||||
{Name: "postgresql_database", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_DATABASE", DefaultValue: "registry", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "postgresql_host", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_HOST", DefaultValue: "postgresql", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "postgresql_password", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_PASSWORD", DefaultValue: "root123", ItemType: &PasswordType{}, Editable: false},
|
||||
{Name: "postgresql_port", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_PORT", DefaultValue: "5432", ItemType: &IntType{}, Editable: false},
|
||||
{Name: "postgresql_sslmode", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_SSLMODE", DefaultValue: "disable", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "postgresql_username", Scope: SystemScope, Group: DatabaseGroup, EnvKey: "POSTGRESQL_USERNAME", DefaultValue: "postgres", ItemType: &StringType{}, Editable: false},
|
||||
|
||||
{Name: "project_creation_restriction", Scope: UserScope, Group: BasicGroup, EnvKey: "PROJECT_CREATION_RESTRICTION", DefaultValue: common.ProCrtRestrEveryone, ItemType: &StringType{}, Editable: false},
|
||||
{Name: "read_only", Scope: UserScope, Group: BasicGroup, EnvKey: "READ_ONLY", DefaultValue: "false", ItemType: &BoolType{}, Editable: false},
|
||||
|
||||
{Name: "registry_storage_provider_name", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_STORAGE_PROVIDER_NAME", DefaultValue: "filesystem", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "registry_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_URL", DefaultValue: "http://registry:5000", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "registry_controller_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "REGISTRY_CONTROLLER_URL", DefaultValue: "http://registryctl:8080", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "self_registration", Scope: UserScope, Group: BasicGroup, EnvKey: "SELF_REGISTRATION", DefaultValue: "true", ItemType: &BoolType{}, Editable: false},
|
||||
{Name: "token_expiration", Scope: UserScope, Group: BasicGroup, EnvKey: "TOKEN_EXPIRATION", DefaultValue: "30", ItemType: &IntType{}, Editable: false},
|
||||
{Name: "token_service_url", Scope: SystemScope, Group: BasicGroup, EnvKey: "TOKEN_SERVICE_URL", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
|
||||
{Name: "uaa_client_id", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_CLIENTID", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "uaa_client_secret", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_CLIENTSECRET", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "uaa_endpoint", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
{Name: "uaa_verify_cert", Scope: UserScope, Group: UAAGroup, EnvKey: "UAA_VERIFY_CERT", DefaultValue: "false", ItemType: &BoolType{}, Editable: false},
|
||||
|
||||
{Name: "with_chartmuseum", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CHARTMUSEUM", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
|
||||
{Name: "with_clair", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CLAIR", DefaultValue: "true", ItemType: &BoolType{}, Editable: true},
|
||||
{Name: "with_notary", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_NOTARY", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
|
||||
}
|
||||
)
|
122
src/common/config/metadata/type.go
Normal file
122
src/common/config/metadata/type.go
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Type - Use this interface to define and encapsulate the behavior of validation and transformation
|
||||
type Type interface {
|
||||
// validate the configure value
|
||||
validate(str string) error
|
||||
// get the real type of current value, if it is int, return int, if it is string return string etc.
|
||||
get(str string) (interface{}, error)
|
||||
}
|
||||
|
||||
// StringType ...
|
||||
type StringType struct {
|
||||
}
|
||||
|
||||
func (t *StringType) validate(str string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *StringType) get(str string) (interface{}, error) {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// IntType ..
|
||||
type IntType struct {
|
||||
}
|
||||
|
||||
func (t *IntType) validate(str string) error {
|
||||
_, err := strconv.Atoi(str)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetInt ...
|
||||
func (t *IntType) get(str string) (interface{}, error) {
|
||||
return strconv.Atoi(str)
|
||||
}
|
||||
|
||||
// LdapScopeType - The LDAP scope is a int type, but its is limit to 0, 1, 2
|
||||
type LdapScopeType struct {
|
||||
IntType
|
||||
}
|
||||
|
||||
// Validate - Verify the range is limited
|
||||
func (t *LdapScopeType) validate(str string) error {
|
||||
if str == "0" || str == "1" || str == "2" {
|
||||
return nil
|
||||
}
|
||||
return ErrInvalidData
|
||||
}
|
||||
|
||||
// Int64Type ...
|
||||
type Int64Type struct {
|
||||
}
|
||||
|
||||
func (t *Int64Type) validate(str string) error {
|
||||
_, err := strconv.ParseInt(str, 10, 64)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetInt64 ...
|
||||
func (t *Int64Type) get(str string) (interface{}, error) {
|
||||
return strconv.ParseInt(str, 10, 64)
|
||||
}
|
||||
|
||||
// BoolType ...
|
||||
type BoolType struct {
|
||||
}
|
||||
|
||||
func (t *BoolType) validate(str string) error {
|
||||
_, err := strconv.ParseBool(str)
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *BoolType) get(str string) (interface{}, error) {
|
||||
return strconv.ParseBool(str)
|
||||
}
|
||||
|
||||
// PasswordType ...
|
||||
type PasswordType struct {
|
||||
}
|
||||
|
||||
func (t *PasswordType) validate(str string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *PasswordType) get(str string) (interface{}, error) {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// MapType ...
|
||||
type MapType struct {
|
||||
}
|
||||
|
||||
func (t *MapType) validate(str string) error {
|
||||
result := map[string]interface{}{}
|
||||
err := json.Unmarshal([]byte(str), &result)
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *MapType) get(str string) (interface{}, error) {
|
||||
result := map[string]string{}
|
||||
err := json.Unmarshal([]byte(str), &result)
|
||||
return result, err
|
||||
}
|
98
src/common/config/metadata/type_test.go
Normal file
98
src/common/config/metadata/type_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntType_validate(t *testing.T) {
|
||||
test := &IntType{}
|
||||
assert.NotNil(t, test.validate("sample"))
|
||||
assert.Nil(t, test.validate("1000"))
|
||||
|
||||
}
|
||||
|
||||
func TestIntType_get(t *testing.T) {
|
||||
test := &IntType{}
|
||||
result, _ := test.get("1000")
|
||||
assert.IsType(t, result, 1000)
|
||||
}
|
||||
|
||||
func TestStringType_get(t *testing.T) {
|
||||
test := &StringType{}
|
||||
result, _ := test.get("1000")
|
||||
assert.IsType(t, result, "sample")
|
||||
}
|
||||
|
||||
func TestStringType_validate(t *testing.T) {
|
||||
test := &StringType{}
|
||||
assert.Nil(t, test.validate("sample"))
|
||||
}
|
||||
|
||||
func TestLdapScopeType_validate(t *testing.T) {
|
||||
test := &LdapScopeType{}
|
||||
assert.NotNil(t, test.validate("3"))
|
||||
assert.Nil(t, test.validate("2"))
|
||||
}
|
||||
|
||||
func TestInt64Type_validate(t *testing.T) {
|
||||
test := &Int64Type{}
|
||||
assert.NotNil(t, test.validate("sample"))
|
||||
assert.Nil(t, test.validate("1000"))
|
||||
}
|
||||
|
||||
func TestInt64Type_get(t *testing.T) {
|
||||
test := &Int64Type{}
|
||||
result, _ := test.get("32")
|
||||
assert.Equal(t, result, int64(32))
|
||||
}
|
||||
|
||||
func TestBoolType_validate(t *testing.T) {
|
||||
test := &BoolType{}
|
||||
assert.NotNil(t, test.validate("sample"))
|
||||
assert.Nil(t, test.validate("True"))
|
||||
}
|
||||
|
||||
func TestBoolType_get(t *testing.T) {
|
||||
test := &BoolType{}
|
||||
result, _ := test.get("true")
|
||||
assert.Equal(t, result, true)
|
||||
result, _ = test.get("false")
|
||||
assert.Equal(t, result, false)
|
||||
}
|
||||
|
||||
func TestPasswordType_validate(t *testing.T) {
|
||||
test := &PasswordType{}
|
||||
assert.Nil(t, test.validate("zhu88jie"))
|
||||
}
|
||||
|
||||
func TestPasswordType_get(t *testing.T) {
|
||||
test := &PasswordType{}
|
||||
assert.Nil(t, test.validate("zhu88jie"))
|
||||
}
|
||||
|
||||
func TestMapType_validate(t *testing.T) {
|
||||
test := &MapType{}
|
||||
assert.Nil(t, test.validate(`{"sample":"abc", "another":"welcome"}`))
|
||||
assert.NotNil(t, test.validate(`{"sample":"abc", "another":"welcome"`))
|
||||
}
|
||||
|
||||
func TestMapType_get(t *testing.T) {
|
||||
test := &MapType{}
|
||||
result, _ := test.get(`{"sample":"abc", "another":"welcome"}`)
|
||||
assert.Equal(t, result, map[string]string{"sample": "abc", "another": "welcome"})
|
||||
}
|
159
src/common/config/metadata/value.go
Normal file
159
src/common/config/metadata/value.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotDefined ...
|
||||
ErrNotDefined = errors.New("configure item is not defined in metadata")
|
||||
// ErrTypeNotMatch ...
|
||||
ErrTypeNotMatch = errors.New("the required value doesn't matched with metadata defined")
|
||||
// ErrInvalidData ...
|
||||
ErrInvalidData = errors.New("the data provided is invalid")
|
||||
// ErrValueNotSet ...
|
||||
ErrValueNotSet = errors.New("the configure value is not set")
|
||||
)
|
||||
|
||||
// ConfigureValue - struct to hold a actual value, also include the name of config metadata.
|
||||
type ConfigureValue struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// NewConfigureValue ...
|
||||
func NewConfigureValue(name, value string) *ConfigureValue {
|
||||
result := &ConfigureValue{}
|
||||
err := result.Set(name, value)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to set name:%v, value:%v, error %v", name, value, err)
|
||||
result.Name = name // Keep name to trace error
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetString - Get the string value of current configure
|
||||
func (c *ConfigureValue) GetString() string {
|
||||
// Any type has the string value
|
||||
if _, ok := Instance().GetByName(c.Name); ok {
|
||||
return c.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetName ...
|
||||
func (c *ConfigureValue) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// GetInt - return the int value of current value
|
||||
func (c *ConfigureValue) GetInt() int {
|
||||
if item, ok := Instance().GetByName(c.Name); ok {
|
||||
val, err := item.ItemType.get(c.Value)
|
||||
if err != nil {
|
||||
log.Errorf("GetInt failed, error: %+v", err)
|
||||
return 0
|
||||
}
|
||||
if intValue, suc := val.(int); suc {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
log.Errorf("The current value's metadata is not defined, %+v", c)
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetInt64 - return the int64 value of current value
|
||||
func (c *ConfigureValue) GetInt64() int64 {
|
||||
if item, ok := Instance().GetByName(c.Name); ok {
|
||||
val, err := item.ItemType.get(c.Value)
|
||||
if err != nil {
|
||||
log.Errorf("GetInt64 failed, error: %+v", err)
|
||||
return 0
|
||||
}
|
||||
if int64Value, suc := val.(int64); suc {
|
||||
return int64Value
|
||||
}
|
||||
}
|
||||
log.Errorf("The current value's metadata is not defined, %+v", c)
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetBool - return the bool value of current setting
|
||||
func (c *ConfigureValue) GetBool() bool {
|
||||
if item, ok := Instance().GetByName(c.Name); ok {
|
||||
val, err := item.ItemType.get(c.Value)
|
||||
if err != nil {
|
||||
log.Errorf("GetBool failed, error: %+v", err)
|
||||
return false
|
||||
}
|
||||
if boolValue, suc := val.(bool); suc {
|
||||
return boolValue
|
||||
}
|
||||
}
|
||||
log.Errorf("The current value's metadata is not defined, %+v", c)
|
||||
return false
|
||||
}
|
||||
|
||||
// GetStringToStringMap - return the string to string map of current value
|
||||
func (c *ConfigureValue) GetStringToStringMap() map[string]string {
|
||||
result := map[string]string{}
|
||||
if item, ok := Instance().GetByName(c.Name); ok {
|
||||
val, err := item.ItemType.get(c.Value)
|
||||
if err != nil {
|
||||
log.Errorf("The GetBool failed, error: %+v", err)
|
||||
return result
|
||||
}
|
||||
if mapValue, suc := val.(map[string]string); suc {
|
||||
return mapValue
|
||||
}
|
||||
}
|
||||
log.Errorf("GetStringToStringMap failed, current value's metadata is not defined, %+v", c)
|
||||
return result
|
||||
}
|
||||
|
||||
// Validate - to validate configure items, if passed, return nil, else return error
|
||||
func (c *ConfigureValue) Validate() error {
|
||||
if item, ok := Instance().GetByName(c.Name); ok {
|
||||
return item.ItemType.validate(c.Value)
|
||||
}
|
||||
return ErrNotDefined
|
||||
}
|
||||
|
||||
// GetPassword ...
|
||||
func (c *ConfigureValue) GetPassword() string {
|
||||
if _, ok := Instance().GetByName(c.Name); ok {
|
||||
return c.Value
|
||||
}
|
||||
log.Errorf("GetPassword failed, metadata not defined: %v", c.Name)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Set - set this configure item to configure store
|
||||
func (c *ConfigureValue) Set(name, value string) error {
|
||||
if item, ok := Instance().GetByName(name); ok {
|
||||
err := item.ItemType.validate(value)
|
||||
if err == nil {
|
||||
c.Name = name
|
||||
c.Value = value
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return ErrNotDefined
|
||||
}
|
51
src/common/config/metadata/value_test.go
Normal file
51
src/common/config/metadata/value_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testingMetaDataArray = []Item{
|
||||
{Name: "ldap_search_scope", ItemType: &LdapScopeType{}, Scope: "system", Group: "ldapbasic"},
|
||||
{Name: "ldap_search_dn", ItemType: &StringType{}, Scope: "user", Group: "ldapbasic"},
|
||||
{Name: "ulimit", ItemType: &Int64Type{}, Scope: "user", Group: "ldapbasic"},
|
||||
{Name: "ldap_verify_cert", ItemType: &BoolType{}, Scope: "user", Group: "ldapbasic"},
|
||||
{Name: "sample_map_setting", ItemType: &MapType{}, Scope: "user", Group: "ldapbasic"},
|
||||
}
|
||||
|
||||
func TestConfigureValue_GetBool(t *testing.T) {
|
||||
assert.Equal(t, NewConfigureValue("ldap_verify_cert", "true").GetBool(), true)
|
||||
assert.Equal(t, NewConfigureValue("unknown", "false").GetBool(), false)
|
||||
}
|
||||
|
||||
func TestConfigureValue_GetString(t *testing.T) {
|
||||
assert.Equal(t, NewConfigureValue("ldap_url", "ldaps://ldap.vmware.com").GetString(), "ldaps://ldap.vmware.com")
|
||||
}
|
||||
|
||||
func TestConfigureValue_GetStringToStringMap(t *testing.T) {
|
||||
Instance().initFromArray(testingMetaDataArray)
|
||||
assert.Equal(t, NewConfigureValue("sample_map_setting", `{"sample":"abc"}`).GetStringToStringMap(), map[string]string{"sample": "abc"})
|
||||
Instance().init()
|
||||
}
|
||||
func TestConfigureValue_GetInt(t *testing.T) {
|
||||
assert.Equal(t, NewConfigureValue("ldap_timeout", "5").GetInt(), 5)
|
||||
}
|
||||
|
||||
func TestConfigureValue_GetInt64(t *testing.T) {
|
||||
Instance().initFromArray(testingMetaDataArray)
|
||||
assert.Equal(t, NewConfigureValue("ulimit", "99999").GetInt64(), int64(99999))
|
||||
}
|
@ -39,7 +39,11 @@ func NewClient(c *http.Client, modifiers ...modifier.Modifier) *Client {
|
||||
client: c,
|
||||
}
|
||||
if client.client == nil {
|
||||
client.client = &http.Client{}
|
||||
client.client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
}
|
||||
}
|
||||
if len(modifiers) > 0 {
|
||||
client.modifiers = modifiers
|
||||
|
@ -24,9 +24,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
http_error "github.com/goharbor/harbor/src/common/utils/error"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
@ -187,9 +187,9 @@ func send(client *http.Client, req *http.Request) (*AuthContext, error) {
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, &http_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(data),
|
||||
return nil, &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(data),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,19 +16,7 @@ package error
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrDupProject is the error returned when creating a duplicate project
|
||||
var ErrDupProject = errors.New("duplicate project")
|
||||
|
||||
// HTTPError : if response is returned but the status code is not 200, an Error instance will be returned
|
||||
type HTTPError struct {
|
||||
StatusCode int
|
||||
Detail string
|
||||
}
|
||||
|
||||
// Error returns the details as string
|
||||
func (e *HTTPError) Error() string {
|
||||
return fmt.Sprintf("%d %s", e.StatusCode, e.Detail)
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
registry_error "github.com/goharbor/harbor/src/common/utils/error"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||
)
|
||||
|
||||
@ -73,9 +73,9 @@ func getToken(client *http.Client, credential Credential, realm, service string,
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(data),
|
||||
return nil, &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(data),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,11 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
// "time"
|
||||
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
registry_error "github.com/goharbor/harbor/src/common/utils/error"
|
||||
)
|
||||
|
||||
// Registry holds information of a registry entity
|
||||
@ -39,11 +40,13 @@ func init() {
|
||||
defaultHTTPTransport = &http.Transport{}
|
||||
|
||||
secureHTTPTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
},
|
||||
}
|
||||
insecureHTTPTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
@ -118,9 +121,9 @@ func (r *Registry) Catalog() ([]string, error) {
|
||||
suffix = ""
|
||||
}
|
||||
} else {
|
||||
return repos, ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
return repos, &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,8 +152,8 @@ func (r *Registry) Ping() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
return &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ import (
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
registry_error "github.com/goharbor/harbor/src/common/utils/error"
|
||||
)
|
||||
|
||||
// Repository holds information of a repository entity
|
||||
@ -61,7 +61,7 @@ func NewRepository(name, endpoint string, client *http.Client) (*Repository, err
|
||||
|
||||
func parseError(err error) error {
|
||||
if urlErr, ok := err.(*url.Error); ok {
|
||||
if regErr, ok := urlErr.Err.(*registry_error.HTTPError); ok {
|
||||
if regErr, ok := urlErr.Err.(*commonhttp.Error); ok {
|
||||
return regErr
|
||||
}
|
||||
}
|
||||
@ -109,9 +109,9 @@ func (r *Repository) ListTag() ([]string, error) {
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
return tags, ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
return tags, &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
|
||||
}
|
||||
@ -149,9 +149,9 @@ func (r *Repository) ManifestExist(reference string) (digest string, exist bool,
|
||||
return
|
||||
}
|
||||
|
||||
err = ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
err = &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -186,9 +186,9 @@ func (r *Repository) PullManifest(reference string, acceptMediaTypes []string) (
|
||||
return
|
||||
}
|
||||
|
||||
err = ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
err = &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
|
||||
return
|
||||
@ -221,9 +221,9 @@ func (r *Repository) PushManifest(reference, mediaType string, payload []byte) (
|
||||
return
|
||||
}
|
||||
|
||||
err = ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
err = &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
|
||||
return
|
||||
@ -252,9 +252,9 @@ func (r *Repository) DeleteManifest(digest string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
return &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,8 +288,8 @@ func (r *Repository) DeleteTag(tag string) error {
|
||||
}
|
||||
|
||||
if !exist {
|
||||
return ®istry_error.HTTPError{
|
||||
StatusCode: http.StatusNotFound,
|
||||
return &commonhttp.Error{
|
||||
Code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,9 +323,9 @@ func (r *Repository) BlobExist(digest string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
return false, &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
}
|
||||
|
||||
@ -359,9 +359,9 @@ func (r *Repository) PullBlob(digest string) (size int64, data io.ReadCloser, er
|
||||
return
|
||||
}
|
||||
|
||||
err = ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
err = &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
|
||||
return
|
||||
@ -390,9 +390,9 @@ func (r *Repository) initiateBlobUpload(name string) (location, uploadUUID strin
|
||||
return
|
||||
}
|
||||
|
||||
err = ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
err = &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
|
||||
return
|
||||
@ -424,9 +424,9 @@ func (r *Repository) monolithicBlobUpload(location, digest string, size int64, d
|
||||
return err
|
||||
}
|
||||
|
||||
return ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
return &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
}
|
||||
|
||||
@ -462,9 +462,9 @@ func (r *Repository) DeleteBlob(digest string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ®istry_error.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
return &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
registry_error "github.com/goharbor/harbor/src/common/utils/error"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/utils/test"
|
||||
)
|
||||
|
||||
@ -392,10 +392,10 @@ func TestListTag(t *testing.T) {
|
||||
|
||||
func TestParseError(t *testing.T) {
|
||||
err := &url.Error{
|
||||
Err: ®istry_error.HTTPError{},
|
||||
Err: &commonhttp.Error{},
|
||||
}
|
||||
e := parseError(err)
|
||||
if _, ok := e.(*registry_error.HTTPError); !ok {
|
||||
if _, ok := e.(*commonhttp.Error); !ok {
|
||||
t.Errorf("error type does not match registry error")
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ const (
|
||||
UsersURLSuffix = "/Users"
|
||||
)
|
||||
|
||||
var uaaTransport = &http.Transport{}
|
||||
var uaaTransport = &http.Transport{Proxy: http.ProxyFromEnvironment}
|
||||
|
||||
// Client provides funcs to interact with UAA.
|
||||
type Client interface {
|
||||
|
@ -32,7 +32,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/clair"
|
||||
registry_error "github.com/goharbor/harbor/src/common/utils/error"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/common/utils/notary"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||
@ -265,8 +264,8 @@ func (ra *RepositoryAPI) Delete() {
|
||||
if err != nil {
|
||||
log.Errorf("error occurred while listing tags of %s: %v", repoName, err)
|
||||
|
||||
if regErr, ok := err.(*registry_error.HTTPError); ok {
|
||||
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
||||
if regErr, ok := err.(*commonhttp.Error); ok {
|
||||
ra.CustomAbort(regErr.Code, regErr.Message)
|
||||
}
|
||||
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
@ -312,12 +311,12 @@ func (ra *RepositoryAPI) Delete() {
|
||||
return
|
||||
}
|
||||
if err = rc.DeleteTag(t); err != nil {
|
||||
if regErr, ok := err.(*registry_error.HTTPError); ok {
|
||||
if regErr.StatusCode == http.StatusNotFound {
|
||||
if regErr, ok := err.(*commonhttp.Error); ok {
|
||||
if regErr.Code == http.StatusNotFound {
|
||||
continue
|
||||
}
|
||||
log.Errorf("failed to delete tag %s: %v", t, err)
|
||||
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
||||
ra.CustomAbort(regErr.Code, regErr.Message)
|
||||
}
|
||||
log.Errorf("error occurred while deleting tag %s:%s: %v", repoName, t, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
@ -751,8 +750,8 @@ func (ra *RepositoryAPI) GetManifests() {
|
||||
if err != nil {
|
||||
log.Errorf("error occurred while getting manifest of %s:%s: %v", repoName, tag, err)
|
||||
|
||||
if regErr, ok := err.(*registry_error.HTTPError); ok {
|
||||
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
||||
if regErr, ok := err.(*commonhttp.Error); ok {
|
||||
ra.CustomAbort(regErr.Code, regErr.Message)
|
||||
}
|
||||
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
|
@ -21,10 +21,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/clair"
|
||||
registry_error "github.com/goharbor/harbor/src/common/utils/error"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry/auth"
|
||||
@ -273,7 +273,7 @@ func buildReplicationActionURL() string {
|
||||
func repositoryExist(name string, client *registry.Repository) (bool, error) {
|
||||
tags, err := client.ListTag()
|
||||
if err != nil {
|
||||
if regErr, ok := err.(*registry_error.HTTPError); ok && regErr.StatusCode == http.StatusNotFound {
|
||||
if regErr, ok := err.(*commonhttp.Error); ok && regErr.Code == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
|
@ -135,6 +135,7 @@ func initProjectManager() error {
|
||||
}
|
||||
AdmiralClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: pool,
|
||||
},
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
er "github.com/goharbor/harbor/src/common/utils/error"
|
||||
@ -286,20 +287,20 @@ func (d *driver) Create(pro *models.Project) (int64, error) {
|
||||
// Maybe a 409 error will be returned if Admiral team finds the way to
|
||||
// return a specific code in Xenon.
|
||||
// The following codes convert both those two errors to DupProjectErr
|
||||
httpErr, ok := err.(*er.HTTPError)
|
||||
httpErr, ok := err.(*commonhttp.Error)
|
||||
if !ok {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if httpErr.StatusCode == http.StatusConflict {
|
||||
if httpErr.Code == http.StatusConflict {
|
||||
return 0, er.ErrDupProject
|
||||
}
|
||||
|
||||
if httpErr.StatusCode != http.StatusInternalServerError {
|
||||
if httpErr.Code != http.StatusInternalServerError {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
match, e := regexp.MatchString(dupProjectPattern, httpErr.Detail)
|
||||
match, e := regexp.MatchString(dupProjectPattern, httpErr.Message)
|
||||
if e != nil {
|
||||
log.Errorf("failed to match duplicate project mattern: %v", e)
|
||||
}
|
||||
@ -397,9 +398,9 @@ func (d *driver) send(method, path string, body io.Reader) ([]byte, error) {
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, &er.HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Detail: string(b),
|
||||
return nil, &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(b),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,6 +230,7 @@ func getDBFromConfig(cfg map[string]interface{}) *models.Database {
|
||||
postgresql.Username = cfg[common.PostGreSQLUsername].(string)
|
||||
postgresql.Password = cfg[common.PostGreSQLPassword].(string)
|
||||
postgresql.Database = cfg[common.PostGreSQLDatabase].(string)
|
||||
postgresql.SSLMode = cfg[common.PostGreSQLSSLMode].(string)
|
||||
database.PostGreSQL = postgresql
|
||||
|
||||
return database
|
||||
|
@ -24,6 +24,7 @@
|
||||
"styles": [
|
||||
"node_modules/@clr/icons/clr-icons.min.css",
|
||||
"node_modules/@clr/ui/clr-ui.min.css",
|
||||
"node_modules/swagger-ui/dist/swagger-ui.css",
|
||||
"node_modules/prismjs/themes/prism-solarizedlight.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
@ -35,7 +36,10 @@
|
||||
"node_modules/web-animations-js/web-animations.min.js",
|
||||
"node_modules/marked/lib/marked.js",
|
||||
"node_modules/prismjs/prism.js",
|
||||
"node_modules/prismjs/components/prism-yaml.min.js"
|
||||
"node_modules/prismjs/components/prism-yaml.min.js",
|
||||
"node_modules/jquery/dist/jquery.slim.js",
|
||||
"node_modules/popper.js/dist/umd/popper.js",
|
||||
"node_modules/bootstrap/dist/js/bootstrap.js"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
|
@ -1,8 +1,7 @@
|
||||
<div>
|
||||
<h2 class="h2-log-override" *ngIf="withTitle">{{'SIDE_NAV.LOGS' | translate}}</h2>
|
||||
<div class="row flex-items-xs-between flex-items-xs-bottom">
|
||||
<div></div>
|
||||
<div class="action-head-pos rightPos">
|
||||
<div class="action-head-pos">
|
||||
<div class="select filterTag" [hidden]="!isOpenFilterTag">
|
||||
<select id="selectKey" (change)="selectFilterKey($event)">
|
||||
<option value="username">{{"AUDIT_LOG.USERNAME" | translate | lowercase}}</option>
|
||||
|
@ -92,7 +92,7 @@ export const LabelColor = [
|
||||
|
||||
export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' };
|
||||
|
||||
export const DefaultHelmIcon = '/images/helm-gray.png';
|
||||
export const DefaultHelmIcon = '/images/helm-gray.svg';
|
||||
|
||||
export enum Roles {
|
||||
PROJECT_ADMIN = 1,
|
||||
|
@ -75,7 +75,7 @@
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length===1 && developerRoleOrAbove)" (click)="retag(selectedRow)"><clr-icon shape="copy" size="16"></clr-icon> {{'REPOSITORY.RETAG' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length===1 && guestRoleOrAbove)" (click)="retag(selectedRow)"><clr-icon shape="copy" size="16"></clr-icon> {{'REPOSITORY.RETAG' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasProjectAdminRole" (click)="deleteTags(selectedRow)" [disabled]="!selectedRow.length"><clr-icon shape="times" size="16"></clr-icon> {{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column class="width-130" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
|
@ -761,6 +761,10 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
public get developerRoleOrAbove(): boolean {
|
||||
return this.memberRoleID === Roles.DEVELOPER || this.hasProjectAdminRole;
|
||||
return this.memberRoleID === Roles.DEVELOPER || this.hasProjectAdminRole;
|
||||
}
|
||||
|
||||
public get guestRoleOrAbove(): boolean {
|
||||
return this.memberRoleID === Roles.GUEST || this.memberRoleID === Roles.DEVELOPER || this.hasProjectAdminRole;
|
||||
}
|
||||
}
|
||||
|
705
src/portal/package-lock.json
generated
705
src/portal/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -32,18 +32,26 @@
|
||||
"@clr/angular": "^0.12.10",
|
||||
"@clr/icons": "^0.12.0",
|
||||
"@clr/ui": "^0.12.0",
|
||||
"@fortawesome/fontawesome-free": "^5.1.0-4",
|
||||
"@ng-bootstrap/ng-bootstrap": "^2.0.0",
|
||||
"@ngx-translate/core": "^10.0.2",
|
||||
"@ngx-translate/http-loader": "^3.0.1",
|
||||
"@types/jquery": "^2.0.41",
|
||||
"@webcomponents/custom-elements": "^1.1.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"buffer": "^5.2.1",
|
||||
"core-js": "^2.5.4",
|
||||
"intl": "^1.2.5",
|
||||
"jquery": "^3.3.1",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ng-packagr": "^4.1.1",
|
||||
"ngx-clipboard": "^11.1.1",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
"ngx-markdown": "^6.2.0",
|
||||
"popper.js": "^1.14.3",
|
||||
"rxjs": "^6.3.1",
|
||||
"stream": "^0.0.2",
|
||||
"swagger-ui": "^3.20.2",
|
||||
"ts-helpers": "^1.1.1",
|
||||
"tslib": "^1.9.0",
|
||||
"types": "^0.1.1",
|
||||
|
@ -42,7 +42,7 @@ export class AppConfig {
|
||||
this.project_creation_restriction = "everyone";
|
||||
this.self_registration = true;
|
||||
this.has_ca_root = false;
|
||||
this.harbor_version = "1.2.0";
|
||||
this.harbor_version = "unknown";
|
||||
this.clair_vulnerability_status = {
|
||||
overall_last_update: 0,
|
||||
details: []
|
||||
|
@ -20,6 +20,7 @@ import { HarborRoutingModule } from './harbor-routing.module';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { AccountModule } from './account/account.module';
|
||||
import { ConfigurationModule } from './config/config.module';
|
||||
import { DeveloperCenterModule } from './dev-center/dev-center.module';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
@ -30,6 +31,7 @@ import { ProjectConfigComponent } from './project/project-config/project-config.
|
||||
import zh from '@angular/common/locales/zh-Hans';
|
||||
import es from '@angular/common/locales/es';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import { DevCenterComponent } from './dev-center/dev-center.component';
|
||||
registerLocaleData(zh, 'zh-cn');
|
||||
registerLocaleData(es, 'es-es');
|
||||
registerLocaleData(localeFr, 'fr-fr');
|
||||
@ -49,7 +51,7 @@ export function getCurrentLanguage(translateService: TranslateService) {
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ProjectConfigComponent,
|
||||
ProjectConfigComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@ -57,7 +59,8 @@ export function getCurrentLanguage(translateService: TranslateService) {
|
||||
BaseModule,
|
||||
AccountModule,
|
||||
HarborRoutingModule,
|
||||
ConfigurationModule
|
||||
ConfigurationModule,
|
||||
DeveloperCenterModule
|
||||
],
|
||||
exports: [
|
||||
],
|
||||
|
@ -26,7 +26,6 @@ import { GlobalSearchComponent } from './global-search/global-search.component';
|
||||
import { FooterComponent } from './footer/footer.component';
|
||||
import { HarborShellComponent } from './harbor-shell/harbor-shell.component';
|
||||
import { SearchResultComponent } from './global-search/search-result.component';
|
||||
import { StartPageComponent } from './start-page/start.component';
|
||||
|
||||
import { SearchTriggerService } from './global-search/search-trigger.service';
|
||||
|
||||
@ -46,7 +45,6 @@ import { SearchTriggerService } from './global-search/search-trigger.service';
|
||||
FooterComponent,
|
||||
HarborShellComponent,
|
||||
SearchResultComponent,
|
||||
StartPageComponent
|
||||
],
|
||||
exports: [ HarborShellComponent ],
|
||||
providers: [SearchTriggerService]
|
||||
|
@ -2,7 +2,8 @@
|
||||
<global-message [isAppLevel]="true"></global-message>
|
||||
<navigator (showAccountSettingsModal)="openModal($event)" (showPwdChangeModal)="openModal($event)"></navigator>
|
||||
<div class="content-container">
|
||||
<div class="content-area" [class.container-override]="showSearch" [class.content-area-override]="!shouldOverrideContent" [class.start-content-padding]="shouldOverrideContent">
|
||||
<div class="content-area" [class.container-override]="showSearch" [class.content-area-override]="!shouldOverrideContent"
|
||||
[class.start-content-padding]="shouldOverrideContent">
|
||||
<global-message [isAppLevel]="false"></global-message>
|
||||
<!-- Only appear when searching -->
|
||||
<search-result></search-result>
|
||||
@ -17,46 +18,39 @@
|
||||
<clr-icon shape="list" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.LOGS' | translate}}
|
||||
</a>
|
||||
<clr-vertical-nav-group *ngIf="isSystemAdmin" routerLinkActive="active">
|
||||
<a clrVerticalNavLink target="_blank" routerLink="/devcenter" routerLinkActive="active">
|
||||
<clr-icon shape="terminal" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.DEVCENTER' | translate}}
|
||||
</a>
|
||||
<clr-vertical-nav-group *ngIf="isSystemAdmin" routerLinkActive="active">
|
||||
<clr-icon shape="administrator" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}
|
||||
<a routerLink="#" hidden aria-hidden="true"></a>
|
||||
<clr-vertical-nav-group-children *clrIfExpanded="true">
|
||||
<a clrVerticalNavLink
|
||||
routerLink="/harbor/users"
|
||||
routerLinkActive="active">
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}
|
||||
<a clrVerticalNavLink routerLink="/harbor/users" routerLinkActive="active">
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}
|
||||
</a>
|
||||
<a *ngIf='isLdapMode'
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/groups"
|
||||
routerLinkActive="active">
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
<a *ngIf='isLdapMode' clrVerticalNavLink routerLink="/harbor/groups" routerLinkActive="active">
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.GROUP' | translate}}
|
||||
</a>
|
||||
<a clrVerticalNavLink
|
||||
routerLink="/harbor/registries"
|
||||
routerLinkActive="active">
|
||||
<clr-icon shape="block" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.REGISTRY' | translate}}
|
||||
<a clrVerticalNavLink routerLink="/harbor/registries" routerLinkActive="active">
|
||||
<clr-icon shape="block" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.REGISTRY' | translate}}
|
||||
</a>
|
||||
<a clrVerticalNavLink
|
||||
routerLink="/harbor/replications"
|
||||
routerLinkActive="active">
|
||||
<clr-icon shape="cloud-traffic" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}
|
||||
<a clrVerticalNavLink routerLink="/harbor/replications" routerLinkActive="active">
|
||||
<clr-icon shape="cloud-traffic" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}
|
||||
</a>
|
||||
<a clrVerticalNavLink
|
||||
routerLink="/harbor/configs"
|
||||
routerLinkActive="active">
|
||||
<clr-icon shape="cog" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}}
|
||||
<a clrVerticalNavLink routerLink="/harbor/configs" routerLinkActive="active">
|
||||
<clr-icon shape="cog" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}}
|
||||
</a>
|
||||
</clr-vertical-nav-group-children>
|
||||
</clr-vertical-nav-group>
|
||||
</clr-vertical-nav>
|
||||
<hbr-operation-model *ngIf="isUserExisting"></hbr-operation-model>
|
||||
<hbr-operation-model *ngIf="isUserExisting"></hbr-operation-model>
|
||||
</div>
|
||||
</clr-main-container>
|
||||
<account-settings-modal></account-settings-modal>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="branding">
|
||||
<a href="javascript:void(0)" class="nav-link" (click)="homeAction()">
|
||||
<!-- <clr-icon shape="vm-bug" *ngIf="!customStyle?.headerLogo"></clr-icon> -->
|
||||
<img src="../../../images/harbor-white-logo.svg" class="harbor-logo" />
|
||||
<img [src]="'images/harbor-logo.svg'" class="harbor-logo" />
|
||||
<img [attr.src]="'static/images/'+customStyle?.headerLogo" *ngIf="customStyle?.headerLogo" class="headerLogo">
|
||||
<span class="title">{{customProjectName?.projectName? customProjectName?.projectName:(appTitle | translate)}}</span>
|
||||
</a>
|
||||
|
@ -1,29 +0,0 @@
|
||||
<!-- Authenticated-->
|
||||
<div class="row row-fill-height row-margin" *ngIf="isSessionValid">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
|
||||
<statistics-panel></statistics-panel>
|
||||
<top-repo></top-repo>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Guest -->
|
||||
<div class="row row-fill-height" *ngIf="!isSessionValid">
|
||||
<div class="col-xs-12 col-sm-12 col-md-5 col-lg-5 col-xl-5 column-fill-height">
|
||||
<div class="start-card">
|
||||
<div class="card-img my-card-img">
|
||||
</div>
|
||||
<div class="card-block">
|
||||
<h3 class="card-title">Getting Start</h3>
|
||||
<p class="card-text">
|
||||
{{'START_PAGE.GETTING_START' | translate}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer my-card-footer">
|
||||
<a href="https://goharbor.io" target="_blank" class="btn btn-sm btn-link">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-md-7 col-lg-7 col-xl-7">
|
||||
<top-repo></top-repo>
|
||||
</div>
|
||||
</div>
|
@ -1,30 +0,0 @@
|
||||
.start-card {
|
||||
border-right: 1px solid #cccccc;
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.row-fill-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.row-margin {
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
.column-fill-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.my-card-img {
|
||||
background-image: url('../../../images/harbor-logo.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.my-card-footer {
|
||||
float: right;
|
||||
margin-top: 100px;
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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.
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
@Component({
|
||||
selector: 'start-page',
|
||||
templateUrl: "start.component.html",
|
||||
styleUrls: ['start.component.scss']
|
||||
})
|
||||
export class StartPageComponent implements OnInit {
|
||||
isSessionValid: boolean = false;
|
||||
|
||||
constructor(
|
||||
private session: SessionService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isSessionValid = this.session.getCurrentUser() != null;
|
||||
}
|
||||
}
|
2
src/portal/src/app/dev-center/dev-center.component.html
Normal file
2
src/portal/src/app/dev-center/dev-center.component.html
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
<div class="swagger-container" style="overflow:scroll;"></div>
|
8
src/portal/src/app/dev-center/dev-center.component.scss
Normal file
8
src/portal/src/app/dev-center/dev-center.component.scss
Normal file
@ -0,0 +1,8 @@
|
||||
.swagger-container {
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
26
src/portal/src/app/dev-center/dev-center.component.spec.ts
Normal file
26
src/portal/src/app/dev-center/dev-center.component.spec.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DevCenterComponent } from './dev-center.component';
|
||||
|
||||
describe('DevCenterComponent', () => {
|
||||
let component: DevCenterComponent;
|
||||
let fixture: ComponentFixture<DevCenterComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DevCenterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DevCenterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
37
src/portal/src/app/dev-center/dev-center.component.ts
Normal file
37
src/portal/src/app/dev-center/dev-center.component.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { AfterViewInit, Component, ElementRef, OnInit } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
import { throwError as observableThrowError, Observable } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
|
||||
|
||||
const SwaggerUI = require('swagger-ui');
|
||||
@Component({
|
||||
selector: 'dev-center',
|
||||
templateUrl: 'dev-center.component.html',
|
||||
styleUrls: ['dev-center.component.scss']
|
||||
})
|
||||
export class DevCenterComponent implements AfterViewInit {
|
||||
private ui: any;
|
||||
private host: any;
|
||||
private json: any;
|
||||
constructor(private el: ElementRef, private http: Http) {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.http.get("/swagger.json")
|
||||
.pipe(catchError(error => observableThrowError(error)))
|
||||
.pipe(map(response => response.json())).subscribe(json => {
|
||||
json.host = window.location.host;
|
||||
const protocal = window.location.protocol;
|
||||
json.schemes = [protocal.replace(":", "")];
|
||||
let ui = SwaggerUI({
|
||||
spec: json,
|
||||
domNode: this.el.nativeElement.querySelector('.swagger-container'),
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUI.presets.apis
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
23
src/portal/src/app/dev-center/dev-center.module.ts
Normal file
23
src/portal/src/app/dev-center/dev-center.module.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { DevCenterComponent } from "./dev-center.component";
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
RouterModule.forChild([{
|
||||
path: "**",
|
||||
component: DevCenterComponent,
|
||||
}]),
|
||||
ClarityModule.forRoot(),
|
||||
],
|
||||
declarations: [
|
||||
DevCenterComponent,
|
||||
],
|
||||
})
|
||||
export class DeveloperCenterModule {}
|
@ -22,6 +22,7 @@ import { MemberGuard } from './shared/route/member-guard-activate.service';
|
||||
import { PageNotFoundComponent } from './shared/not-found/not-found.component';
|
||||
import { HarborShellComponent } from './base/harbor-shell/harbor-shell.component';
|
||||
import { ConfigurationComponent } from './config/config.component';
|
||||
import { DevCenterComponent } from './dev-center/dev-center.component';
|
||||
|
||||
import { UserComponent } from './user/user.component';
|
||||
import { SignInComponent } from './account/sign-in/sign-in.component';
|
||||
@ -43,7 +44,7 @@ import { LeavingRepositoryRouteDeactivate } from './shared/route/leaving-reposit
|
||||
import { ProjectComponent } from './project/project.component';
|
||||
import { ProjectDetailComponent } from './project/project-detail/project-detail.component';
|
||||
import { MemberComponent } from './project/member/member.component';
|
||||
import {ProjectLabelComponent} from "./project/project-label/project-label.component";
|
||||
import { ProjectLabelComponent } from "./project/project-label/project-label.component";
|
||||
import { ProjectConfigComponent } from './project/project-config/project-config.component';
|
||||
import { ProjectRoutingResolver } from './project/project-routing-resolver.service';
|
||||
import { ListChartsComponent } from './project/list-charts/list-charts.component';
|
||||
@ -53,6 +54,10 @@ import { ChartDetailComponent } from './project/chart-detail/chart-detail.compon
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
{ path: 'reset_password', component: ResetPasswordComponent },
|
||||
{
|
||||
path: 'devcenter',
|
||||
component: DevCenterComponent
|
||||
},
|
||||
{
|
||||
path: 'harbor',
|
||||
component: HarborShellComponent,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="false">
|
||||
<div class="modal-body dialog-body">
|
||||
<div class="harbor-logo-black">
|
||||
<img [src]="'images/harbor-black-logo.png'">
|
||||
<img [src]="'images/harbor-logo.svg'" class="harbor-icon">
|
||||
</div>
|
||||
<div class="content">
|
||||
<div>{{customName?.projectName? customName?.projectName : ('APP_TITLE.HARBOR' | translate)}}</div>
|
||||
|
@ -15,4 +15,10 @@
|
||||
|
||||
.content {
|
||||
margin:0 10px 10px 10px;
|
||||
}
|
||||
|
||||
.harbor-icon {
|
||||
transform: translateX(-100%);
|
||||
width: 56px;
|
||||
filter: drop-shadow(rgb(0, 0, 0) 58px 2px);
|
||||
}
|
@ -47,7 +47,7 @@ export class AboutDialogComponent implements OnInit {
|
||||
|
||||
public get version(): string {
|
||||
let appConfig = this.appConfigService.getConfig();
|
||||
return appConfig ? appConfig.harbor_version : "n/a";
|
||||
return appConfig.harbor_version;
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
|
@ -121,7 +121,8 @@
|
||||
"GROUP": "Groups",
|
||||
"REGISTRY": "Registries",
|
||||
"REPLICATION": "Replications",
|
||||
"CONFIG": "Configuration"
|
||||
"CONFIG": "Configuration",
|
||||
"DEVCENTER": "Developer Center"
|
||||
},
|
||||
"LOGS": "Logs"
|
||||
},
|
||||
@ -444,7 +445,7 @@
|
||||
"DELETION_TITLE_TAG": "Confirm Tag Deletion",
|
||||
"DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?",
|
||||
"DELETION_TITLE_TAG_DENIED": "Signed tag cannot be deleted",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.\nDelete from Notary via this command:\n{{param}}",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted.\nDelete from Notary via this command:\n",
|
||||
"TAGS_NO_DELETE": "Delete is prohibited in read only mode.",
|
||||
"FILTER_FOR_REPOSITORIES": "Filter Repositories",
|
||||
"TAG": "Tag",
|
||||
|
@ -121,7 +121,8 @@
|
||||
"REGISTRY": "Registries",
|
||||
"GROUP": "Groups",
|
||||
"REPLICATION": "Replicacións",
|
||||
"CONFIG": "Configuración"
|
||||
"CONFIG": "Configuración",
|
||||
"DEVCENTER": "Developer Center"
|
||||
},
|
||||
"LOGS": "Logs"
|
||||
},
|
||||
@ -442,7 +443,7 @@
|
||||
"DELETION_TITLE_TAG": "Confirmación de Eliminación de Etiqueta",
|
||||
"DELETION_SUMMARY_TAG": "¿Quiere eliminar la etiqueta {{param}}?",
|
||||
"DELETION_TITLE_TAG_DENIED": "La etiqueta firmada no puede ser eliminada",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "La etiqueta debe ser eliminada de la Notaría antes de eliminarla.\nEliminarla de la Notaría con este comando:\n{{param}}",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "La etiqueta debe ser eliminada de la Notaría antes de eliminarla.\nEliminarla de la Notaría con este comando:\n",
|
||||
"TAGS_NO_DELETE": "Delete is prohibited in read only mode.",
|
||||
"FILTER_FOR_REPOSITORIES": "Filtrar Repositorios",
|
||||
"TAG": "Etiqueta",
|
||||
|
@ -107,7 +107,8 @@
|
||||
"USER": "Utilisateurs",
|
||||
"GROUP": "Groups",
|
||||
"REPLICATION": "Réplication",
|
||||
"CONFIG": "Configuration"
|
||||
"CONFIG": "Configuration",
|
||||
"DEVCENTER": "Developer Center"
|
||||
},
|
||||
"LOGS": "Logs"
|
||||
},
|
||||
@ -422,7 +423,7 @@
|
||||
"DELETION_TITLE_TAG": "Confirmer la suppression du Tag",
|
||||
"DELETION_SUMMARY_TAG": "Voulez-vous supprimer le tag {{param}}?",
|
||||
"DELETION_TITLE_TAG_DENIED": "Un tag signé ne peut être supprimé",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "La balise doit être supprimée du Résumé avant qu'elle ne puisse être supprimée. \nSupprimer du Résumé via cette commande: \n{{param}}",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "La balise doit être supprimée du Résumé avant qu'elle ne puisse être supprimée. \nSupprimer du Résumé via cette commande: \n",
|
||||
"TAGS_NO_DELETE": "Upload/Delete is prohibited in read only mode.",
|
||||
"FILTER_FOR_REPOSITORIES": "Filtrer les Dépôts",
|
||||
"TAG": "Tag",
|
||||
|
@ -119,7 +119,8 @@
|
||||
"GROUP": "Grupos",
|
||||
"REGISTRY": "Registros",
|
||||
"REPLICATION": "Replicações",
|
||||
"CONFIG": "Configuração"
|
||||
"CONFIG": "Configuração",
|
||||
"DEVCENTER": "Developer Center"
|
||||
},
|
||||
"LOGS": "Logs"
|
||||
},
|
||||
@ -442,7 +443,7 @@
|
||||
"DELETION_TITLE_TAG": "Confirmar remoção de Tag",
|
||||
"DELETION_SUMMARY_TAG": "Você quer remover a Tag {{param}}?",
|
||||
"DELETION_TITLE_TAG_DENIED": "Tags assinadas não podem ser removidas",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "A tag deve ser removida do Notary antes de ser apagada.\nRemova do Notary com o seguinte comando:\n{{param}}",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "A tag deve ser removida do Notary antes de ser apagada.\nRemova do Notary com o seguinte comando:\n",
|
||||
"TAGS_NO_DELETE": "Remover é proibido em modo somente leitura.",
|
||||
"FILTER_FOR_REPOSITORIES": "Filtrar repositórios",
|
||||
"TAG": "Tag",
|
||||
|
@ -120,7 +120,8 @@
|
||||
"GROUP": "组管理",
|
||||
"REGISTRY": "仓库管理",
|
||||
"REPLICATION": "复制管理",
|
||||
"CONFIG": "配置管理"
|
||||
"CONFIG": "配置管理",
|
||||
"DEVCENTER": "开发者中心"
|
||||
},
|
||||
"LOGS": "日志"
|
||||
},
|
||||
@ -442,7 +443,7 @@
|
||||
"DELETION_TITLE_TAG": "删除镜像标签确认",
|
||||
"DELETION_SUMMARY_TAG": "确认删除镜像标签 {{param}}?",
|
||||
"DELETION_TITLE_TAG_DENIED": "已签名的镜像不能被删除",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n{{param}}",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "要删除此镜像标签必须首先从Notary中删除。\n请执行如下Notary命令删除:\n",
|
||||
"TAGS_NO_DELETE": "在只读模式下删除是被禁止的",
|
||||
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
|
||||
"TAG": "标签",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6.3 KiB |
1
src/portal/src/images/helm-gray.svg
Normal file
1
src/portal/src/images/helm-gray.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 121.02 126.41"><defs><style>.ed7893be-f72e-4d49-983e-3ddcacf196ab{fill:#a2a2a2;}</style></defs><path class="ed7893be-f72e-4d49-983e-3ddcacf196ab" d="M453.41,121.78c-.91-.64-2-2.73-2.61-5.06a37.72,37.72,0,0,1-.38-10.23,12,12,0,0,0,.17-1.89,16.89,16.89,0,0,0-1.89-.29,46.62,46.62,0,0,1-16.5-5.26,13.83,13.83,0,0,0-2-1,5,5,0,0,0-1.12,1.65c-2.2,4.23-6,8.42-8.94,9.79a4.18,4.18,0,0,1-3.14.27c-1-.53-1.28-2.63-.69-5a29.48,29.48,0,0,1,3.35-6.89,38.1,38.1,0,0,1,2.55-3.26l1.5-1.68-.78-.74c-2.06-2-5.92-6.51-5.92-7a61.13,61.13,0,0,1,5.65-3.92c.2-.06.67.41,1.31,1.29a45.53,45.53,0,0,0,7.25,7.17,37.93,37.93,0,0,0,52.57-7,6.94,6.94,0,0,1,1.45-1.56A55.26,55.26,0,0,1,490.88,85c.16.26-2.11,3.22-4.06,5.26a16.35,16.35,0,0,0-1.68,1.94,15.29,15.29,0,0,0,1.62,1.88c2.81,3,5.57,7.59,6.34,10.61.59,2.32.3,4.42-.69,5a3.58,3.58,0,0,1-1.26.2c-3,0-8-4.87-11-10.68a9.12,9.12,0,0,0-1.23-2,6.61,6.61,0,0,0-1.3.72A46.94,46.94,0,0,1,461.11,104a13.84,13.84,0,0,0-2.38.52c-.15.12-.13.68.07,1.94a37.74,37.74,0,0,1-.26,9.73,11,11,0,0,1-2.19,5c-1.07,1.17-1.91,1.34-3,.6ZM393.88,76.39c0-.14-.07-7.52,0-16.4l.06-16.16h8.46l.06,6c0,4.45.11,6,.3,6.1a39,39,0,0,0,4.71.17c3.47,0,4.51-.06,4.68-.27s.24-2.21.28-6.11V43.88h8.47V76.59h-8.47V70.35c0-4.2-.13-6.33-.28-6.51s-1.21-.27-4.71-.27-4.53.06-4.71.27-.24,2.31-.28,6.51v6.24l-4.19.06C394.92,76.64,394,76.59,393.88,76.39Zm34.26,0c0-.14-.07-7.52,0-16.41V43.83h21l.06,3.58V51H436.8l-.06,2.41c0,1.79,0,2.45.2,2.57a47.92,47.92,0,0,0,5.36.17h5.09l-.06,3.58v3.59l-5.24.1-5.24.1v5.65l6.45.1,6.45.1v7.07l-10.74,0c-8.48,0-10.76,0-10.84-.2Zm28.22,0c0-.14-.07-7.52,0-16.41V43.83h8.46l.11,12.72.1,12.72,6.14.1,6.15.1v7.07l-10.43,0c-8.25,0-10.46,0-10.54-.2Zm26.81,0c0-.14-.07-7.52,0-16.4l.06-16.16h4.43a38.16,38.16,0,0,1,4.66.11c.3.2,4.77,12.39,5.76,15.69s.95,3.23,1.18,3a21.38,21.38,0,0,0,1.06-3c1-3.41,5.17-15.23,5.44-15.59.15-.19,1.24-.24,4.65-.2h4.46V76.55l-3.77.06-3.78,0-.13-.53c-.28-1.11.09-12.8.51-16.13.67-5.26.56-5.34-1-.81-1.69,4.89-5,13.89-5.36,14.38S500.9,74,499,74c-1.43,0-2.15-.08-2.29-.26s-4.36-11.51-5.48-14.93c-.8-2.42-1.23-3.29-1.22-2.44,0,.2.19,1.86.42,3.7.41,3.43.78,14.93.51,16l-.13.53H487c-2.8,0-3.76-.06-3.83-.25Zm2.66-37.29a39.91,39.91,0,0,0-4.48-5.64,37.5,37.5,0,0,0-20-11.07c-2.31-.5-2.92-.54-7.16-.55-5,0-6.09.11-10,1.12A37.22,37.22,0,0,0,430,30.22a46.3,46.3,0,0,0-6.64,7c-.86,1.19-1.28,1.64-1.51,1.55a43.94,43.94,0,0,1-5.65-3.8c0-.48,3.32-4.63,5.57-7l2.33-2.43-1.54-1.67c-2.81-3.05-5.51-7.59-6.26-10.54-.59-2.33-.3-4.42.69-5a3.37,3.37,0,0,1,1.26-.21c3,0,8.05,4.87,11,10.68a10.93,10.93,0,0,0,1.2,2,13.21,13.21,0,0,0,1.75-.91,47,47,0,0,1,16.65-5.27,8.92,8.92,0,0,0,1.78-.28c.18-.11.16-.65-.1-2.21a35.31,35.31,0,0,1,.28-10.81c1-3.7,2.55-5.77,4.14-5.47s2.93,2.71,3.68,6.2a40.46,40.46,0,0,1,0,10.77,4.56,4.56,0,0,0-.2,1.56,13,13,0,0,0,2.15.45,45,45,0,0,1,17.27,6.36c.4.24.79.36.85.26s.61-1.19,1.22-2.41c2.36-4.73,6.17-9,9.28-10.48a4.18,4.18,0,0,1,3.14-.27c1,.53,1.28,2.62.69,5a29.92,29.92,0,0,1-3.57,7.21,33.88,33.88,0,0,1-3.13,3.74l-1.87,1.89,1.84,2A59.77,59.77,0,0,1,492,35.46c.27.53.28.67,0,.88a52.14,52.14,0,0,1-5.68,3.34c-.13,0-.4-.27-.6-.6Z" transform="translate(-393.85 4.18)"/></svg>
|
After Width: | Height: | Size: 3.1 KiB |
@ -37,3 +37,6 @@ import 'intl/locale-data/jsonp/zh';
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
|
||||
(window as any).global = window;
|
||||
// @ts-ignore
|
||||
window.Buffer = window.Buffer || require('buffer').Buffer;
|
||||
|
@ -8,6 +8,10 @@
|
||||
"es6",
|
||||
"dom"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
"jasmine"
|
||||
],
|
||||
"mapRoot": "./",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"es2015",
|
||||
"es2017",
|
||||
"dom"
|
||||
],
|
||||
"noImplicitAny": false,
|
||||
|
@ -34,7 +34,7 @@ type secretHandler struct {
|
||||
secrets map[string]string
|
||||
}
|
||||
|
||||
// NewSecretHandler creaters a new authentiation handler which adds
|
||||
// NewSecretHandler creates a new authentication handler which adds
|
||||
// basic authentication credentials to a request.
|
||||
func NewSecretHandler(secrets map[string]string) AuthenticationHandler {
|
||||
return &secretHandler{
|
||||
|
@ -15,6 +15,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@ -49,3 +50,45 @@ func TestAuthorizeRequestValid(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestNilRequest(t *testing.T) {
|
||||
secret := "Correct"
|
||||
req, err := http.NewRequest("", "", nil)
|
||||
req = nil
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
_ = commonsecret.AddToRequest(req, secret)
|
||||
|
||||
authenticator := NewSecretHandler(map[string]string{"secret1": "correct"})
|
||||
err = authenticator.AuthorizeRequest(req)
|
||||
assert.Equal(t, err, ErrNoSecret)
|
||||
}
|
||||
|
||||
func TestNoSecret(t *testing.T) {
|
||||
secret := ""
|
||||
req, err := http.NewRequest("", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
_ = commonsecret.AddToRequest(req, secret)
|
||||
|
||||
authenticator := NewSecretHandler(map[string]string{})
|
||||
err = authenticator.AuthorizeRequest(req)
|
||||
assert.Equal(t, err, ErrNoSecret)
|
||||
}
|
||||
|
||||
func TestIncorrectHarborSecret(t *testing.T) {
|
||||
secret := "correct"
|
||||
req, err := http.NewRequest("", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
_ = commonsecret.AddToRequest(req, secret)
|
||||
|
||||
// Set req header to an incorrect value to trigger error return
|
||||
req.Header.Set("Authorization", fmt.Sprintf("%s%s", "WrongPrefix", secret))
|
||||
authenticator := NewSecretHandler(map[string]string{"secret1": "correct"})
|
||||
err = authenticator.AuthorizeRequest(req)
|
||||
assert.Equal(t, err, ErrInvalidCredential)
|
||||
}
|
||||
|
@ -11,12 +11,6 @@ def pull_harbor_image(registry, username, password, image, tag, expected_error_m
|
||||
time.sleep(2)
|
||||
_docker_api.docker_image_pull(r'{}/{}'.format(registry, image), tag = tag, expected_error_message = expected_error_message)
|
||||
|
||||
def pull_harbor_image_successfully(registry, username, password, image, tag):
|
||||
pull_harbor_image(registry, username, password, image, tag)
|
||||
|
||||
def pull_harbor_image_unsuccessfully(registry, username, password, image, tag, expected_error_message):
|
||||
pull_harbor_image(registry, username, password, image, tag, expected_error_message = expected_error_message)
|
||||
|
||||
def push_image_to_project(project_name, registry, username, password, image, tag):
|
||||
_docker_api = DockerAPI()
|
||||
_docker_api.docker_login(registry, username, password)
|
||||
@ -95,7 +89,7 @@ class Repository(base.Base):
|
||||
raise Exception("Image should be <Not Scanned> state!")
|
||||
|
||||
def check_image_scan_result(self, repo_name, tag, expected_scan_status = "finished", **kwargs):
|
||||
timeout_count = 20
|
||||
timeout_count = 30
|
||||
while True:
|
||||
time.sleep(5)
|
||||
timeout_count = timeout_count - 1
|
||||
|
@ -10,8 +10,7 @@ from library.project import Project
|
||||
from library.user import User
|
||||
from library.repository import Repository
|
||||
from library.repository import push_image_to_project
|
||||
from library.repository import pull_harbor_image_successfully
|
||||
from library.repository import pull_harbor_image_unsuccessfully
|
||||
from library.repository import pull_harbor_image
|
||||
|
||||
class TestProjects(unittest.TestCase):
|
||||
@classmethod
|
||||
@ -77,13 +76,13 @@ class TestProjects(unittest.TestCase):
|
||||
self.repo.image_should_exist(TestProjects.repo_name, tag, **TestProjects.USER_CONTENT_TRUST_CLIENT)
|
||||
|
||||
#5. Pull image(IA) successfully;
|
||||
pull_harbor_image_successfully(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag)
|
||||
pull_harbor_image(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag)
|
||||
|
||||
#6. Enable content trust in project(PA) configuration;
|
||||
self.project.update_project(TestProjects.project_content_trust_id, metadata = {"enable_content_trust": "true"}, **TestProjects.USER_CONTENT_TRUST_CLIENT)
|
||||
|
||||
#7. Pull image(IA) failed and the reason is "The image is not signed in Notary".
|
||||
pull_harbor_image_unsuccessfully(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag, "The image is not signed in Notary")
|
||||
pull_harbor_image(harbor_server, admin_name, admin_password, TestProjects.repo_name, tag, expected_error_message = "The image is not signed in Notary")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -73,8 +73,8 @@ class TestProjects(unittest.TestCase):
|
||||
#Note: Please make sure that this Image has never been pulled before by any other cases,
|
||||
# so it is a not-scanned image rigth after rpository creation.
|
||||
#image = "tomcat"
|
||||
image = "pypy"
|
||||
src_tag = "latest"
|
||||
image = "docker"
|
||||
src_tag = "1.13"
|
||||
#5. Create a new repository(RA) and tag(TA) in project(PA) by user(UA);
|
||||
TestProjects.repo_name, tag = push_image_to_project(project_scan_image_name, harbor_server, user_scan_image_name, user_001_password, image, src_tag)
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
\c notaryserver;
|
||||
|
||||
ALTER TABLE tuf_files OWNER TO server;
|
||||
ALTER SEQUENCE tuf_files_id_seq OWNER TO server;
|
||||
ALTER TABLE change_category OWNER TO server;
|
||||
ALTER TABLE changefeed OWNER TO server;
|
||||
ALTER SEQUENCE changefeed_id_seq OWNER TO server;
|
||||
ALTER TABLE schema_migrations OWNER TO server;
|
@ -1,7 +1,7 @@
|
||||
\c notaryserver;
|
||||
|
||||
CREATE TABLE "tuf_files" (
|
||||
"id" int PRIMARY KEY,
|
||||
"id" serial PRIMARY KEY,
|
||||
"created_at" timestamp NULL DEFAULT NULL,
|
||||
"updated_at" timestamp NULL DEFAULT NULL,
|
||||
"deleted_at" timestamp NULL DEFAULT NULL,
|
||||
|
@ -0,0 +1,5 @@
|
||||
\c notarysigner;
|
||||
|
||||
ALTER TABLE private_keys OWNER TO signer;
|
||||
ALTER SEQUENCE private_keys_id_seq OWNER TO signer;
|
||||
ALTER TABLE schema_migrations OWNER TO signer;
|
@ -1,7 +1,7 @@
|
||||
\c notarysigner;
|
||||
|
||||
CREATE TABLE "private_keys" (
|
||||
"id" int PRIMARY KEY,
|
||||
"id" serial PRIMARY KEY,
|
||||
"created_at" timestamp NULL DEFAULT NULL,
|
||||
"updated_at" timestamp NULL DEFAULT NULL,
|
||||
"deleted_at" timestamp NULL DEFAULT NULL,
|
||||
|
@ -73,8 +73,11 @@ EOF
|
||||
# launch_pgsql $PGSQL_USR
|
||||
psql -U $1 -f /harbor-migration/db/schema/notaryserver_create_tables.pgsql
|
||||
psql -U $1 -f /harbor-migration/db/schema/notaryserver_insert_data.pgsql
|
||||
psql -U $1 -f /harbor-migration/db/schema/notaryserver_alter_tables.pgsql
|
||||
|
||||
psql -U $1 -f /harbor-migration/db/schema/notarysigner_create_tables.pgsql
|
||||
psql -U $1 -f /harbor-migration/db/schema/notarysigner_insert_data.pgsql
|
||||
psql -U $1 -f /harbor-migration/db/schema/notarysigner_alter_tables.pgsql
|
||||
|
||||
stop_mysql root
|
||||
stop_pgsql $1
|
||||
|
@ -95,6 +95,7 @@ def convert_notary_server_db(mysql_dump_file, pgsql_dump_file):
|
||||
write_database(pgsql_dump, "notaryserver")
|
||||
write_insert(pgsql_dump, insert_lines)
|
||||
write_sequence(pgsql_dump, "tuf_files", "id")
|
||||
write_sequence(pgsql_dump, "changefeed", "id")
|
||||
|
||||
def convert_notary_signer_db(mysql_dump_file, pgsql_dump_file):
|
||||
mysql_dump = open(mysql_dump_file)
|
||||
@ -144,7 +145,7 @@ def write_alter_table_bool(pgsql_dump, table_name, table_columnn, default_value=
|
||||
|
||||
def write_sequence(pgsql_dump, table_name, table_columnn):
|
||||
pgsql_dump.write('\n')
|
||||
pgsql_dump.write("CREATE SEQUENCE %s_%s_seq;\n" % (table_name, table_columnn))
|
||||
pgsql_dump.write("CREATE SEQUENCE IF NOT EXISTS %s_%s_seq;\n" % (table_name, table_columnn))
|
||||
pgsql_dump.write("SELECT setval('%s_%s_seq', max(%s)) FROM %s;\n" % (table_name, table_columnn, table_columnn, table_name))
|
||||
pgsql_dump.write("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET DEFAULT nextval('%s_%s_seq');\n" % (table_name, table_columnn, table_name, table_columnn))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user