提交 ccccd7c6 编写于 作者: landylee007's avatar landylee007

release v1.0.0

上级
/*
!/cmd
!/lib
!/pkg
!/go.mod
!/go.sum
!/zadig-portal
!/third_party
---
name: "Bug report"
about: Tell us about a problem you are experiencing
---
/kind bug
**What steps did you take and what happened:**
[A clear and concise description of what the bug is.]
**What did you expect to happen:**
**Anything else you would like to add:**
[Miscellaneous information that will assist in solving the issue.]
blank_issues_enabled: false
contact_links:
- name: xx
url: xx
about: xx
---
name: Enhancement
about: Suggest enhancements to existing features
---
/kind enhancement
**Is your enhancement proposal related to a problem? Please describe.**
[A clear and concise description of what the problem is.]
**Describe the solution you'd like**
[A clear and concise description of what you want to happen.]
**Describe alternatives you've considered**
[A clear and concise description of any alternative solutions or features you've considered.]
**Additional context**
[Add any other context or graphics about the feature request here.]
---
name: Feature request
about: Suggest an idea for this project
---
/kind feature
**Is your feature request related to a problem? Please describe.**
[A clear and concise description of what the problem is. Ex. I'm always frustrated when ... ]
**Describe the feature you'd like**
[A clear and concise description of what you want to happen.]
**Describe alternatives you've considered**
[A clear and concise description of any alternative solutions or features you've considered.]
**Additional context**
[Add any other context or screenshots about the feature request here.]
---
name: Ask a Question
about: I want to ask a question.
---
/kind question
## General Question
<!--
Before asking a question, make sure you have:
- Googled your question.
- Searched open and closed [GitHub issues](https://github.com/koderover/zadig/issues?utf8=%E2%9C%93&q=is%3Aissue)
-->
# for https://mlbot.net
label-alias:
bug: 'kind/bug'
feature: 'kind/feature'
question: 'kind/question'
\ No newline at end of file
### What this PR does / Why we need it:
Issue Number: close #xxx <!-- REMOVE this line if no issue to close -->
Problem Summary:
### What is changed and how it works?
What's Changed:
How it Works:
### Check List <!--REMOVE the items that are not applicable-->
- [ ] Docs have been added / updated
- [ ] Unit test / Integration test for the changes have been added
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No code
## More information
\ No newline at end of file
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
.idea
.DS_Store
bin
.cache
gin-bin
.mock
# Files
*.tar.gz
*.dat
*.local.*
*.out
node_modules
bower_components
#website
zadig-portal/dist
zadig-portal/node_modules/
zadig-portal/dist/
zadig-portal/npm-debug.log
zadig-portal/test/unit/coverage
zadig-portal/test/e2e/reports
zadig-portal/selenium-debug.log
zadig-portal/yarn-error.log
#Go doc website
godoc-proxy/website/node_modules/
godoc-proxy/website/dist
npm-debug.log
.bowerrc
/.editorconfig
.jshintrc
.sass-cache/
.tmp/
dist/
.cover
.scannerwork/
coverage.html
temp_coverage_test.go
# VS Code IDE
.vscode
docker/*.tar.gz
*.patch
main
test/integration/reporters/*.xml
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
contact@koderover.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
# Zadig 贡献指南
首先,非常感谢你使用 Zadig
Zadig 是一套分布式开源的持续部署系统,和其它 CI/CD 不同,Zadig 不仅可以提供高可用的 CI/CD 能力,同时内置很多面向不同技术场景的最佳实践。
Zadig 的成长离不开大家的支持。我们欢迎各类贡献,小到修改错别字、更新文档链接,大到负责从设计到研发的一个完整的功能。如果你愿意为其贡献代码或提供建议,
请阅读以下内容。
## 目录
- [Zadig 贡献指南](#zadig-贡献指南)
- [目录](#目录)
- [先决条件](#先决条件)
- [环境设置](#环境设置)
- [贡献方式 1 - 提交 issue](#贡献方式-1---提交-issue)
- [Issue 提交后会被如何处理?](#issue-提交后会被如何处理)
- [贡献方式 2 - 更改文档](#贡献方式-2---更改文档)
- [简单的文档改动](#简单的文档改动)
- [进阶的文档改动](#进阶的文档改动)
- [贡献方式 3 - 提交代码](#贡献方式-3---提交代码)
- [简单的代码改动](#简单的代码改动)
- [进阶的代码改动](#进阶的代码改动)
- [贡献者资源](#贡献者资源)
- [PR / Commit 指导](#pr--commit-指导)
- [贡献者进阶之路](#贡献者进阶之路)
- [如何获取帮助](#如何获取帮助)
- [其它资源](#其它资源)
## 先决条件
每个 commit 都需要加上 Developer Certificate of Origin。这是一个很轻量的操作,具体可以见[这里](https://github.com/probot/dco#how-it-works)
同时还想提醒大家遵循社区的 [Code of Conduct](CODE_OF_CONDUCT.md),Zadig的良好发展离不开一个健康的社区,希望大家能一起维护。
## 环境设置
请先fork一份对应的仓库,不要直接在仓库下建分支。然后可以参考 [Zadig 开发流程](community/dev/contributor-workflow.md) 的介绍将 Zadig 环境搭建起来。
在决定提交issue或者提交任何更改之前,请查一下项目的 [open issues](https://github.com/koderover/Zadig/issues),避免重复。我们也准备
了几个 issue label 来帮助大家筛选:
1) 如果你想找一些适合上手的 issue,可以看下 [#good-first-issue](https://github.com/koderover/Zadig/labels/good%20first%20issue)
有没有你感兴趣的。
2) 如果你在找更进阶点的贡献,可以看下 [#help-wanted](https://github.com/koderover/Zadig/labels/help%20wanted) label 下的内容。
3) 如果你想找些 bug 来 fix,可以看下 [#bugs](https://github.com/koderover/Zadig/labels/bug)
## 贡献方式 1 - 提交 issue
**如果想上报的是和 security 相关的问题,请不要通过提交issue,而是发邮件到 contact@koderover.com。** 如果不是 security 相关的问题,请接着阅读这一章节。
贡献者提交 issue 的时候,有以下五种类型需要考虑:
1. [`documentation`](https://github.com/koderover/Zadig/labels/documentation)
2. [`bug`](https://github.com/koderover/Zadig/labels/bug)
3. [`feature request`](https://github.com/koderover/Zadig/labels/feature%20request)
4. [`question`](https://github.com/koderover/Zadig/labels/question)
5. [`enhancement`](https://github.com/koderover/Zadig/labels/enhancement)
如果贡献者知道自己的issue是明确关于哪个或者哪几个服务的,也建议将服务相对应的label加上去:具体请[搜索我们带`service/`前缀的 label](https://github.com/koderover/Zadig/labels?q=service%2F);如果不确定的话可以放着,我们的 maintainer 会加上。
请首先检查下我们的 [open issues](https://github.com/koderover/Zadig/issues),确保不要提交重复的 issue。确认没有重复后,请选择上面四种类型之一的 label。并且按 issue 模板填好,尽可能详细的解释你的 issue —— 原则是要让没有你 context 的别人也能很容易的看懂。
### Issue 提交后会被如何处理?
我们项目的 [maintainer](GOVERNANCE.md#maintainers) 会监控所有的 issue,并做相应的处理:
1. 他们会再次确认新创建的 issue 是不是添加了上述五种 label 里正确的 label,如果不是的话他们会进行更新。
2. 他们同时也会决定是不是 accept issue,参见下一条。
3. 如果适用的话,他们可能会将以下四种新的 tag 加到 issue 上:
1) [`duplicate`](https://github.com/koderover/Zadig/labels/duplicate): 重复的issue
2) [`wonfix`](https://github.com/koderover/Zadig/labels/wontfix):决定不采取行动。maintainer 会说明不修复的具体原因,比如
work as intended, obsolete, infeasible, out of scope
3) [`good first issue`](https://github.com/koderover/Zadig/labels/good%20first%20issue):见上文,适合新人上手的 issue。
4) [`good intermediate issue`](https://github.com/koderover/Zadig/labels/good%20intermediate%20issue): 见上文,比较
进阶的 issue,欢迎社区的贡献者来挑战。
4. issue 如果没有被关掉的话,现在就正式可以被认领(在issue上留言)了。
5. Maintainer 同时也会定期的检查和清理所有 issue,移除过期的 issue。
## 贡献方式 2 - 更改文档
### 简单的文档改动
对于非常简单的文档改动,比如改个错别字、更新个链接之类的,不需要走什么流程,直接建一个 PR 就行。我们的 Maintainer 会去 review。对于 PR 的具体要求,请参见
我们的 [PR / Commit 指导](#pr--commit-指导)
### 进阶的文档改动
如果你想对文档做一些复杂点的改动,比如重塑文档的结构、增加一个新文档、添加几个章节等等,具体要求请遵循[进阶的代码改动](#进阶的代码改动)。你将会需要一个 issue 来跟踪,并且需要先提交你的改动方案并且方案被我们的 maintainer 通过。
## 贡献方式 3 - 提交代码
对于**任何**的代码改动,你**都需要有相应的 issue 来跟踪**:不管是现有的 issue 还是[创建一个新的 issue](#贡献方式-1---提交-issue)
> 请在对应的issue下留言,表明你要WORK ON这个issue,避免重复
### 简单的代码改动
对于简单的代码改动,我们的指导如下:
1. 你可以在对应的 issue 上简单描述下你的设计方案,收取反馈;当然如果你很自信改动非常简单直观并且你的改动基本不可能有什么问题的话,你也完全可以跳过这个步骤。
2. 做相应的改动 - 具体的指导见 [Zadig 开发流程](community/dev/contributor-workflow.md)
3. 遵循我们 [PR / Commit 指导](#pr--commit-指导),提交 PR,我们的 maintainer 会去 review。
### 进阶的代码改动
对于没那么直观或者稍微复杂点的代码改动:
1. 你需要先写一个设计文档:
1) 复制一份我们的设计方案模板 [Zadig design template](community/rfc/yyyy-MM-dd-my-design-template.md)
2) 根据模板,描述下你想做什么和你的设计思路,以及相应的注意事项,比如数据库改动、向前兼容性考虑等。
3) 文档尽量做到简洁、清楚。
2. 将你的设计方案按`yyyy-MM-dd-my-design-name.md`格式命名,并且放到 [community/rfc](community/rfc)目录下。为这个设计方案提交
一个单独的 PR,我们的 maintainer 会去 review。
3. 如果这个设计方案被采纳并且 PR 被合并了,你就可以按照你设计文档中提供的方案做相应的修改了。
4. 我们强烈建议保持 PR 的原子性,如果你的项目可以被拆分成更细粒度的子任务,请尽量做拆分然后每一个子任务发一个单独的 PR。
5. 对于#4中提到的每一个子任务,参考上文非常轻量的 [简单的代码改动](#简单的代码改动) 指导。
## 贡献者资源
### PR / Commit 指导
- 请遵循我们的 PR 模板,并且尽可能清晰、详细的描述你的 PR。
- 请保持每个 PR 的原子性,并且 PR 里的每条 commit message 都做到简洁、清晰、准确。
- 记得每条 commit 都要签名 [DCO](https://github.com/probot/dco#how-it-works)
- 我们建议开发者尽早建立 PR,没必要等到全部工作都完成了再建 PR。只不过记得给 PR 的标题加上 WIP 前缀 -- "WIP: [title]"。当 PR 完成后可以 review 时再把
前缀去掉。在那之后我们的 maintainer 就会去 review 了。
- 确保你的 PR 通过所有测试。
- 对于代码风格,请参考官方的 [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
- 为你的 PR 添加上合适的 label(s),具体怎么选择 label 和 issue 参见[贡献方式 1 - 提交 issue](#贡献方式-1---提交-issue)
- 完整测试你改动后的代码--参考[如何调试代码](community/dev/contributor-workflow.md#4-调试)
### 贡献者进阶之路
我们有清晰的开发者的晋级之路,请参见我们的 [GOVERNANCE](GOVERNANCE.md) 文档。
### 如何获取帮助
- 邮箱:contact@koderover.com
- 欢迎加入[slack channel](https://join.slack.com/t/zadig-workspace/shared_invite/zt-qedvct1t-mQUf2eyTRkoVCc_RWKKgxw)
### 其它资源
- [项目的 maintainers](GOVERNANCE.md#maintainers)
# Contributing Guidelines
First, thank you for considering contributions to Zadig!
We welcome all sorts of contributions: from fixing a typo, updating a documentation, raising a bug, proposing a feature request, to proposing a design and driving all the implementations in end-to-end manner.
## TOC
- [Contributing Guidelines](#contributing-guidelines)
- [TOC](#toc)
- [Prerequisite](#prerequisite)
- [Setup](#setup)
- [Contribution option 1 - reporting an issue](#contribution-option-1---reporting-an-issue)
- [How issues will be handled](#how-issues-will-be-handled)
- [Contribution option 2 - update a doc](#contribution-option-2---update-a-doc)
- [For trivial doc changes](#for-trivial-doc-changes)
- [For non-trivial doc changes](#for-non-trivial-doc-changes)
- [Contribution option 3 - code changes](#contribution-option-3---code-changes)
- [For trivial changes](#for-trivial-changes)
- [For non-trivial changes](#for-non-trivial-changes)
- [Contributor resources](#contributor-resources)
- [PR and commit guidelines](#pr-and-commit-guidelines)
- [Growth path for contributors](#growth-path-for-contributors)
- [Where can you get support](#where-can-you-get-support)
- [More resources](#more-resources)
## Prerequisite
For every commit, **be sure to sign off on the [DCO](https://github.com/probot/dco#how-it-works).**
Zadig's success heavily relies on a healthy community, and it's up to every one of us to maintain it. This is a
reminder that all contributions are expected to adhere to our [Code of Conduct](CODE_OF_CONDUCT.md).
## Setup
You should first **fork the specific repository** you want to contribute to. Please follow the instructions
[here](community/dev/contributor-workflow.md) to set Zadig up for running.
Before submitting any new issues or proposing any new changes, please check the
[open issues](https://github.com/koderover/Zadig/issues) to make sure the efforts aren't duplicated. There are
a few labels that could help you navigate through the issues:
- If you are looking for some startup issues to get your hands on, you could follow the
[#good-first-issue](https://github.com/koderover/Zadig/labels/good%20first%20issue) issues.
- If you are looking for some more serious challenges, you could check out our
[#help-wanted](https://github.com/koderover/Zadig/labels/help%20wanted) issues.
- Or some random bugfix, check out [#bugs](https://github.com/koderover/Zadig/labels/bug).
## Contribution option 1 - reporting an issue
**For security related issues, please drop an email to contact@koderover.com instead.** For non-security related
issues, please read on.
There are 5 types of labels for issues that you need to think of:
1. [`documentation`](https://github.com/koderover/Zadig/labels/documentation)
2. [`bug`](https://github.com/koderover/Zadig/labels/bug)
3. [`feature request`](https://github.com/koderover/Zadig/labels/feature%20request)
4. [`question`](https://github.com/koderover/Zadig/labels/question)
5. [`enhancement`](https://github.com/koderover/Zadig/labels/enhancement)
If you understand which services are involved with the new issue, please also attach the relevant label(s) to it. You
could [search for `service/` prefix](https://github.com/koderover/Zadig/labels?q=service%2F) to find the correct label.
If you aren't sure about this, feel free to leave it open, our maintainers will get to it.
If you have checked that no duplicated issues existed and decided to create a new issue, choose the label accordingly
and follow the issue template. Please be as specific as possible assuming low or no context from whoever reading the
issue.
### How issues will be handled
All issues created will be triaged by our maintainers:
1. The maintainers will double check the issues were created with the proper label, update them otherwise.
2. They'll make decisions whether the issues will be accepted, see next point.
3. Certain tags might be applied by our maintainers accordingly, there are mainly 4 of them:
1) [`duplicate`](https://github.com/koderover/Zadig/labels/duplicate)
2) [`wonfix`](https://github.com/koderover/Zadig/labels/wontfix):
rationales will be provided, e.g. work as intended, obsolete, infeasible, out of scope
3) [`good first issue`](https://github.com/koderover/Zadig/labels/good%20first%20issue): good candidates
suitable for onboarding.
4) [`good intermediate issue`](https://github.com/koderover/Zadig/labels/good%20intermediate%20issue): a more
serious challenge that is up for grabs to the community.
4. The issues are open for grab now.
5. Periodically, the maintainers will double check that all issues are up-to-date, stale ones will be removed.
## Contribution option 2 - update a doc
### For trivial doc changes
For trivial doc changes, such as fixing a typo, updating a link, go ahead and create a pull request. One of our
maintainers will get to it. Please follow our [PR and commit guidelines](#pr-and-commit-guidelines) below.
### For non-trivial doc changes
If you are looking for a more serious documentation changes, such as refactoring a full page, please follow the
[instructions of non-trivial code changes](#for-non-trivial-changes) below. You'll need an issue to track this and a
design outlining your proposal and have the design approved first.
## Contribution option 3 - code changes
For any code changes, you **need to have an associated issue**: either an existing one or
[create a new one](#contribution-option-1---reporting-an-issue).
> to prevent duplication, please claim an issue by commenting on it that you are working on the issue.
### For trivial changes
1. You could describe your design ideas in the issue; but if you are confident enough with your
change, feel free to skip this step too.
2. Work on the changes in your forked repository.
3. Please follow our [PR and commit guidelines](#pr-and-commit-guidelines), one of our maintainers will review your PR.
### For non-trivial changes
1. You need to write a design to describe what you are trying to address and how by making a copy of
[Zadig design template](community/rfc/yyyy-MM-dd-my-design-template.md) and filling in the template.
2. Add the design in markdown format in [community/rfc folder](community/rfc), name it as
`yyyy-MM-dd-my-design-name.md`. Please send a separate PR for this. One of our maintainers will get to it.
3. Once the design is approved and PR merged, you can start working on the issue following the solutions outlined in
the design.
4. We highly encourage atomic PRs, so if your change can be broken down into several smaller sub-tasks, please do it
and create one PR for each.
5. For each sub-task, follow the [trivial-changes guidelines above](#for-trivial-changes).
## Contributor resources
### PR and commit guidelines
- Please follow our PR template and write clear descriptions as much as possible.
- Please keep each commit atomic and write crisp, clear and accurate commit messages.
- Don't forget signing off the [DCO](https://github.com/probot/dco#how-it-works) for each commit.
- We recommend you to create PR **early** once you decide to work on something. However, it must be prefaced with
"WIP: [title]" until it's ready for review. Once it is ready for review, remove the prefix "WIP: ", one of our
maintainers will get to it.
- Make sure your changes passed all the tests.
- Please rely on the official [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) as the
code style guideline.
- Attach the relevant label(s) to your PR by following the same guideline for issues in [Contribution option 1 - reporting an issue](#contribution-option-1---reporting-an-issue)
- To test your changes, please follow [how to debug your code](community/dev/contributor-workflow.md#4-调试)
### Growth path for contributors
We have established a well-lit path for contributor's progression. Please check out [GOVERNANCE](GOVERNANCE.md) for
more information.
### Where can you get support
- Email:contact@koderover.com
- [slack channel](https://join.slack.com/t/zadig-workspace/shared_invite/zt-qedvct1t-mQUf2eyTRkoVCc_RWKKgxw)
### More resources
- [Current maintainers](GOVERNANCE.md#maintainers)
\ No newline at end of file
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [Community roles](#community-roles)
- [How to become a contributor](#how-to-become-a-contributor)
- [How to become a maintainer](#how-to-become-a-maintainer)
- [How to become a council member](#how-to-become-a-council-member)
- [Council members](#council-members)
- [Maintainers](#maintainers)
- [Contributors](#contributors)
## Code of Conduct
Please see [CODE OF CONDUCT](CODE_OF_CONDUCT.md)
## Community roles
Besides being the users of Zadig, which is a great way of contributing to the community by itself (and thank you!), there are 3 active community roles:
- Contributor
- Maintainer
- Council member
Note that the latter role is built on top of the former -- i.e. A council member is also a maintainer and a contributor, a maintainer is a contributor as well. Please see the following table for their primary responsibilities:
| Role | Primary responsibilities |
|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Contributor | No particular responsibilities, all healthy contributions are welcome. |
| Maintainer | Technical: review PRs, participate and review designs, responding to issues. Recognized as a solid and respectful contributor.<br>Community: guide the community, role model a high standard of behavior.<br>Strategy: more seasoned maintainers might be consulted by the project council for roadmaps and strategies. |
| Council Member | Technical:<br>Decision maker for key designs.<br>Help people succeed, guide technical directions.<br>Create and maintain effective development processes.<br>Community: <br>Recognize community contributions and promote excellent contributors to advance their roles (e.g. From contributor to maintainer, or from maintainer to be in the council). <br>Guide the value of the community -- set the tone. Ultimately responsible for a healthy community.<br>Strategy: <br>Set strategy, develop roadmap |
| | |
## How to become a contributor?
Anyone can be recognized as a Zadig contributor, as long as they manage to have one PR accepted, answered questions in the community forum, or simply reported an issue.
## How to become a maintainer?
Must be a contributor first, and nominated by an existing maintainer or a council member.
The council will make a decision for the nomination. Normally both the technical and community contribution will be taken into consideration. For example, these are strong signals if the candidate has been a productive contributor, played a key role in driving a few designs, demonstrated both technical skills and enthusiasm towards the project, and been a great citizen for the community.
## How to become a council member?
The candidate has to be a maintainer first and be nominated by 2 council members.
The council will make a decision for the nomination. The candidate has to have demonstrated strategic impact on the project over a relatively long horizon (or in a consistent way), for example,
- Initiated a series of key designs and successfully drove them to be landed
- Having a positive impact over the community such as constantly guiding new members, or consistently answering questions
- Successfully driving the project to be much more impactful and modeling deep understanding in the product and the industry
## Council members
[A list of current council]
## Maintainers
[a list of current maintainers]
## Contributors
- [nighca](https://github.com/nighca)
- [xen0n](https://github.com/xen0n)
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {}
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.
# Zadig
<h3 align="left">开源分布式持续交付产品</h3>
<span align="left">
[![Zadig CI](https://os.koderover.com/api/collie/api/badge?pipelineName=zadig-ci/zadig-ci&source=github&repoFullName=koderover/Zadig&branch=main&eventType=push)](https://os.koderover.com/v1/projects/detail/zadig-ci/pipelines/freestyle/home/zadig-ci/608824fef341de000137317d?rightbar=step)
[![LICENSE](https://img.shields.io/github/license/koderover/zadig.svg)](https://github.com/koderover/zadig/blob/main/LICENSE)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)
![GitHub all releases](https://img.shields.io/github/downloads/koderover/Zadig/total)
</span>
<div align="left">
**[English](./README.md) | 简体中文**
</div>
## 目录
- [Zadig](#zadig)
- [目录](#目录)
- [Zadig 介绍](#zadig-介绍)
- [快速上手](#快速上手)
- [快速使用](#快速使用)
- [快速开发](#快速开发)
- [获取帮助](#获取帮助)
- [更多文档](#更多文档)
- [代码许可](#代码许可)
## Zadig 介绍
Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery)产品,具备高可用 CI/CD 能力,提供云原生运行环境,支持开发者本地联调、微服务并行构建和部署、集成测试等。Zadig 不改变现有流程,无缝集成 Github/Gitlab、Jenkins、多家云厂商等,运维成本极低。
我们的愿景:`工程师 + Zadig = 商业上的成功`
- **高并发的工作流**
基于云原生设计,经过简单配置,系统自动生成工作流,实现多服务高并发执行构建部署测试任务,以解决微服务架构下带来的多服务构建部署效率低下问题。
- **以服务为核心的集成环境**
一套服务配置,分钟级创建多套数据隔离的测试环境。为开发者进行日常调试、为测试人员做集成测试、为产品经理对外 Demo 提供强力支撑。
对于现有的环境无需担心迁移成本,一键托管,轻松浏览、调试环境中的所有服务。
- **无侵入的自动化测试**
便捷且无侵入的对接已有自动化测试框架,通过 GitHub/GitLab Webhook 自动构建、部署及测试。
通过办公通讯机器人为开发者提供第一时间质量反馈,精准高效。有效落地“测试左移”工程实践,让测试价值得到体现。
- **开发本地联调 CLI**
开发本地编辑完代码,一键进行本地代码构建,部署到联调环境,无需再陷入复杂且繁琐的工作流程,让本地联调不再成为难事。解放工程师双手,去创造更多产品价值。
## 快速上手
### 快速使用
请参阅 [快速入门](https://docs.koderover.com/zadig/quick-start/try-out-install)
### 快速开发
请阅读完整的 [Zadig 贡献指南](CONTRIBUTING-zh-CN.md),该包含参与贡献的方式、流程、格式、如何部署、哪里可以获取帮助等。
如果你已经阅读过上面的文档,想快速进入开发状态的话,可以直接进入[Zadig 开发流程](community/dev/contributor-workflow.md)
## 获取帮助
- 更详细的使用说明,见[文档站](https://github.com/koderover/Zadig-doc)
- 如果发现了bug或者功能需求,[欢迎提交issue](CONTRIBUTING-zh-CN.md#贡献方式-1---提交issue)
- 邮箱:contact@koderover.com
- 欢迎加入 [slack channel](https://join.slack.com/t/zadig-workspace/shared_invite/zt-qedvct1t-mQUf2eyTRkoVCc_RWKKgxw)
## 代码许可
[Apache 2.0 License](./LICENSE)
# Zadig
<h3 align="left">Developer-oriented Continuous Delivery Product</h3>
<span align="left">
[![Zadig CI](https://os.koderover.com/api/collie/api/badge?pipelineName=zadig-ci/zadig-ci&source=github&repoFullName=koderover/Zadig&branch=main&eventType=push)](https://os.koderover.com/v1/projects/detail/zadig-ci/pipelines/freestyle/home/zadig-ci/608824fef341de000137317d?rightbar=step)
[![LICENSE](https://img.shields.io/github/license/koderover/zadig.svg)](https://github.com/koderover/zadig/blob/main/LICENSE)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)
![GitHub all releases](https://img.shields.io/github/downloads/koderover/Zadig/total)
</span>
<div align="left">
**English | [简体中文](./README-zh-CN.md)**
</div>
## Table of Contents
- [Zadig](#zadig)
- [Table of Contents](#table-of-contents)
- [What is Zadig](#what-is-zadig)
- [Quick start](#quick-start)
- [How to use?](#how-to-use)
- [How to make contribution?](#how-to-make-contribution)
- [Getting help](#getting-help)
- [More resources](#more-resources)
- [License](#license)
## What is Zadig
Zadig is an open-source, distributed, cloud-native CD (Continuous Delivery) product designed for developers. Zadig not only provides high-availability CI/CD capabilities, but also provides cloud-native operating environments, supports developers' local debugging, parallel build and deployment of microservices, integration testing, etc. .
Zadig is non-invasive, it does not exclude any of your existing development process. Instead it can easily integrate with Github/Gitlab, Jenkins and many other cloud vendors in a seamingless way. We strive for the 10x optimal developer experience with the lowest maintenance cost possible.
> Our vision is: Developer + Zadig = Business success
- **High Concurrency**
Based on cloud-native design, through simple configuration, the system automatically generates workflows to achieve high concurrent execution for continuous delivery relevant tasks such as building, testing and deployment, across multiple services. It significantly improves the efficiency of multi-services deployment in microservice architecture.
- **Service-oriented Environment**
With just one set of service configuration, multiple encapsulated environments will be provided automatically within minutes, empowering independent environments for developers, QAs and product managers.
Minimum to none migration cost of existing environments -- just hosting with one click, the system allows browsing and adjusting all the services at your fingertips.
- **Non-intrusive Testing Automation**
Zadig can easily and non-intrusively embed existing testing automation frameworks, and achieve continuous building, testing and deployment via GitHub/GitLab Webhook.
It also integrates with productivity bots to provide instant quality report, which effectively applies shift-left testing best practices.
- **Convenient Development CLI**
Zadig also provides a convenient toolkit with development commandline interface which allows compiling, building and deploying the changes to dev environment with one command. It enables collaborated debugging and testing with minimum manual toil, reduces cognitive load and allows teams to focus more on business.
## Quick start
### How to use?
Please follow <<[Quick Start](https://docs.koderover.com/zadig/quick-start/try-out-install)>>
### How to make contribution?
Please check out [our contributing guideline](CONTRIBUTING.md).
## Getting help
- More about Zadig, see [here](https://github.com/koderover/Zadig-doc)
- Submit bugs or feature requests following [contributing instructions](CONTRIBUTING.md#contribution-option-1---reporting-an-issue)
- Email:contact@koderover.com
- [Slack channel](https://join.slack.com/t/zadig-workspace/shared_invite/zt-qedvct1t-mQUf2eyTRkoVCc_RWKKgxw)
## License
[Apache 2.0 License](./LICENSE)
/*
Copyright 2021 The KodeRover 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 main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"github.com/koderover/zadig/lib/microservice/aslan/server"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
signals := make(chan os.Signal, 1)
defer close(signals)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
go func() {
select {
case <-signals:
signal.Stop(signals)
cancel()
}
}()
if err := server.Serve(ctx); err != nil {
log.Fatal(err)
}
}
/*
Copyright 2021 The KodeRover 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 main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/koderover/zadig/lib/microservice/cron/server"
)
func main() {
stopCh := make(chan struct{})
signals := make(chan os.Signal, 1)
defer close(signals)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-signals
signal.Stop(signals)
close(stopCh)
}()
if err := server.Serve(stopCh); err != nil {
log.Fatal(err)
}
}
/*
Copyright 2021 The KodeRover 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 main
import (
"log"
"github.com/koderover/zadig/lib/microservice/jenkinsplugin/executor"
)
func main() {
if err := executor.Execute(); err != nil {
log.Fatalf("Failed to run jenkins plugin, the error is: %+v", err)
}
}
/*
Copyright 2021 The KodeRover 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 main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/koderover/zadig/lib/microservice/podexec/server"
)
func main() {
stopCh := make(chan struct{})
signals := make(chan os.Signal, 1)
defer close(signals)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-signals
signal.Stop(signals)
close(stopCh)
}()
if err := server.Serve(stopCh); err != nil {
log.Fatal(err)
}
}
/*
Copyright 2021 The KodeRover 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 main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"github.com/koderover/zadig/lib/microservice/warpdrive/server"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
signals := make(chan os.Signal, 1)
defer close(signals)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
go func() {
select {
case <-signals:
signal.Stop(signals)
cancel()
}
}()
if err := server.Serve(ctx); err != nil {
log.Fatal(err)
}
}
# Zadig 开发流程
## 1. 克隆代码
1.[koderover/Zadig](https://github.com/koderover/zadig) 仓库,点击右上角的 Fork 按钮。
2. 将 fork 后的代码仓库克隆到本地
```bash
git clone git@github.com:<your_github_id>/zadig.git
```
## 2. 本地开发环境搭建
### 前端环境
Zadig 前端使用的 vue 框架,在您贡献代码之前,本地需安装 Node.js 10+、Yarn 以及 NPM 7+。
注意:我们使用 Yarn 进行依赖版本的锁定,所以请使用 Yarn 安装依赖。
### 后端环境
Zadig 后端使用 Go 语言,在您贡献代码之前,本地需安装 Go 1.15+ 版本。
## 3. 贡献代码
请详细阅读 [代码贡献指南](../../CONTRIBUTING-zh-CN.md) 并遵循上面的流程。
## 4. 调试
Zadig 为您提供云上测试环境 [https://os.koderover.com](https://os.koderover.com)
第 1 步:GitHub OAuth 登录系统
第 2 步:Fork Zadig 项目
点击 zadig 选项,将其中的 global.host 中的 `githubid` 改成您的 GitHub ID,否则可能无法访问。
![](./fork-zadig-vars.png)
Fork 完成后,您将获得一个 Zadig 测试环境。
第 3 步 :测试
您可以使用两种测试方式:云上代码测试和本地代码测试。
### 方式一:云上代码测试:
需先提交您的代码,并创建 pull request,调试过程中的 pull request 标题建议加上 WIP。然后使用 [zadig-workflow](https://os.koderover.com/v1/projects/detail/zadig/pipelines/multi/zadig-workflow) 更新您的测试环境。
启动工作流任务需要
1. 选择您代码变更涉及到的服务
2. 选择您提交的 pull request
工作流成功执行后,您可以测试环境进行调试。
### 方式二:本地代码测试
#### 前端本地测试
请确保您的测试环境正常运行,在开始测试之前请将 `zadig-portal/config/index.js` 中的 backEndAddr 改为您测试环境的访问地址。
```
cd zadig-portal
yarn install
yarn run dev
```
访问 http://localhost:8080 进行本地调试。
#### 后端本地测试
使用 Zadig CLI 进行本地调试,使用方式参见 [Zadig CLI 使用指南](https://doc.koderover.com/cli/overview/)
# Design Title
- Author:
- Issue:
- Date:
- Reviewer:
- Review status: pending / reviewing / approved / declined
## Objective
*One or two phrases describing what are you trying to achieve with this design.*
## Background
*Describing the context of this design, such as what are the issues, similar relevant efforts, etc. It should be
understandable by future contributors.*
## Design overview
*A short paragraph describing the key concepts. Such as the main assumption, the key constraints, high-level design
concepts, success criteria, etc.*
## Detailed design
*Outlining your detailed solution here. Please explicitly call out your new API or database changes below if any, also
describe any backward compatibility consideration. Also watch out for any potential concerns over performance,
concurrency, security and privacy.*
### API changes
### Database changes
### Backward compatibility consideration
#golang-deps.Dockerfile.inc
RUN go build -v -o /aslan ./cmd/aslan/main.go
#ubuntu-base.Dockerfile.inc
WORKDIR /app
COPY --from=build /aslan .
ENTRYPOINT ["/app/aslan"]
#golang-deps.Dockerfile.inc
RUN go build -v -o /cron ./cmd/cron/main.go
#ubuntu-base.Dockerfile.inc
WORKDIR /app
COPY --from=build /cron .
ENTRYPOINT ["/app/cron"]
FROM golang:1.15 as build
RUN sed -i -E "s/[a-zA-Z0-9]+.debian.org/mirrors.aliyun.com/g" /etc/apt/sources.list \
&& apt-get update \
&& apt-get install libsasl2-dev
WORKDIR /app
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
ENV GOPROXY=https://goproxy.cn,direct
COPY third_party third_party
COPY go.mod go.sum ./
RUN go mod download
COPY cmd cmd
COPY lib lib
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
OUTPUT=--quiet
if [ "${1:-}" = '--show-diff' ]; then
OUTPUT=
fi
# If a tagged version, just print that tag
HEAD_TAG=$(git tag --points-at HEAD | head -n1)
if [ -n "${HEAD_TAG}" ] ; then
# remove `v` prefix from release name
if echo "${HEAD_TAG}" | grep -Eq "^v[0-9]+(\.[0-9]+)*(-[a-z0-9]+)?$"; then
HEAD_TAG=$(echo "$HEAD_TAG" | cut -c 2-)
fi
echo ${HEAD_TAG}
exit 0
fi
WORKING_SUFFIX=$(if ! git diff --exit-code ${OUTPUT} HEAD >&2; \
then echo "-wip"; \
else echo ""; \
fi)
BRANCH_PREFIX=$(git rev-parse --abbrev-ref HEAD)
# replace spaces with dash
BRANCH_PREFIX=${BRANCH_PREFIX// /-}
# next, replace slashes with dash
BRANCH_PREFIX=${BRANCH_PREFIX//[\/\\]/-}
# now, clean out anything that's not alphanumeric or an dash
BRANCH_PREFIX=${BRANCH_PREFIX//[^a-zA-Z0-9-]/}
# finally, lowercase with TR
BRANCH_PREFIX=`echo -n $BRANCH_PREFIX | tr A-Z a-z`
echo "$BRANCH_PREFIX-$(git rev-parse --short HEAD)$WORKING_SUFFIX"
#golang-deps.Dockerfile.inc
RUN go build -v -o /jenkins-plugin ./cmd/jenkinsplugin/main.go
#ubuntu-base.Dockerfile.inc
WORKDIR /app
COPY --from=build /jenkins-plugin .
ENTRYPOINT ["/app/jenkins-plugin"]
#golang-deps.Dockerfile.inc
RUN go build -v -o /podexec ./cmd/podexec/...
#ubuntu-base.Dockerfile.inc
WORKDIR /app
COPY --from=build /podexec /app/podexec
ENTRYPOINT ["/app/podexec"]
FROM n7832lxy.mirror.aliyuncs.com/library/ubuntu:16.04
# 修改镜像源和时区
RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list \
&& apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates \
&& apt-get install -y \
tzdata \
net-tools \
dnsutils \
ca-certificates \
git \
curl \
lsof \
telnet \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& rm -rf /var/lib/apt/lists/*
#golang-deps.Dockerfile.inc
RUN go build -v -o /warpdrive ./cmd/warpdrive/main.go
#ubuntu-base.Dockerfile.inc
WORKDIR /app
COPY --from=build /warpdrive .
ENTRYPOINT ["/app/warpdrive"]
from ccr.ccs.tencentyun.com/shuhe/nginx:stable
add ./index.html /usr/share/nginx/html
\ No newline at end of file
<h1>
Hello koderover!
</h1>
\ No newline at end of file
---
apiVersion: v1
kind: Service
metadata:
name: nginx3
labels:
app: nginx3
tier: backend
version: "{{.nginxVersion4}}"
spec:
type: NodePort
ports:
- port: 80
selector:
app: nginx3
tier: backend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx3
spec:
replicas: 2
selector:
matchLabels:
app: nginx3
tier: backend
version: "{{.nginxVersion4}}"
template:
metadata:
labels:
app: nginx3
tier: backend
version: "{{.nginxVersion4}}"
spec:
containers:
- name: nginx-test
image: ccr.ccs.tencentyun.com/shuhe/nginx:stable
ports:
- containerPort: 80
volumeMounts:
- name: static-page
mountPath: /usr/share/nginx/html
volumes:
- name: static-page
configMap:
name: static-page
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-expose
spec:
rules:
- host: $EnvName$-nginx-expose.app.8slan.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
path: /
---
apiVersion: v1
kind: ConfigMap
metadata:
name: static-page
labels:
app.kubernetes.io/instance: poetry
app.kubernetes.io/name: poetry-portal-config
data:
index.html: |-
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>{{.customer}} - Sliding Perspective</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
background: #2d303a;
overflow-y: hidden;
}
.bounce {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
color: white;
height: 100%;
font: normal bold 6rem 'Product Sans', sans-serif;
white-space: nowrap;
}
.letter {
animation: bounce 0.75s cubic-bezier(0.05, 0, 0.2, 1) infinite alternate;
display: inline-block;
transform: translate3d(0, 0, 0);
margin-top: 0.5em;
text-shadow: rgba(255, 255, 255, 0.4) 0 0 0.05em;
font: normal 500 6rem 'Varela Round', sans-serif;
color:#fff;
color:#1989fa;
}
.letter:nth-of-type(1) {
animation-delay: -0.083333333s;
}
.letter:nth-of-type(3) {
animation-delay: 0.0833333333s;
}
.letter:nth-of-type(4) {
animation-delay: 0.1666666667s;
}
.letter:nth-of-type(5) {
animation-delay: 0.25s;
}
.letter:nth-of-type(6) {
animation-delay: 0.3333333333s;
}
.letter:nth-of-type(7) {
animation-delay: 0.4166666667s;
}
@keyframes bounce {
0% {
transform: translate3d(0, 0, 0);
text-shadow: rgba(255, 255, 255, 0.4) 0 0 0.05em;
}
100% {
transform: translate3d(0, -1em, 0);
text-shadow: rgba(255, 255, 255, 0.4) 0 1em 0.35em;
}
}
</style>
</head>
<body>
<div class="bounce">
<span class="letter">K</span><span class="letter"></span><span class="letter">o</span><span class="letter">d</span><span class="letter">e</span><span class="letter">R</span
><span class="letter">o</span><span class="letter">v</span><span class="letter">e</span><span class="letter">r</span>
</div>
</body>
</html>
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: redis-master
labels:
app: redis
spec:
selector:
matchLabels:
app: redis
role: master
tier: backend
replicas: 1
template:
metadata:
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: master
image: k8s.gcr.io/redis:e2e # or just image: redis
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 6379
\ No newline at end of file
FROM python:3.6-alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk update && \
apk add bash && \
pip install selenium pytest pytest-html -i https://mirrors.aliyun.com/pypi/simple/
\ No newline at end of file

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2036
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vote", "vote\dotnet\Vote\Vote.csproj", "{9687EAF5-BFF3-4F8D-9C78-1B8EE12CE091}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker", "worker\dotnet\Worker\Worker.csproj", "{083764E8-4C34-43FB-A468-F80CE0ADE10A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Result", "result\dotnet\Result\Result.csproj", "{9AD16D72-E3F5-4A76-814C-40EBD1EE7892}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9687EAF5-BFF3-4F8D-9C78-1B8EE12CE091}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9687EAF5-BFF3-4F8D-9C78-1B8EE12CE091}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9687EAF5-BFF3-4F8D-9C78-1B8EE12CE091}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9687EAF5-BFF3-4F8D-9C78-1B8EE12CE091}.Release|Any CPU.Build.0 = Release|Any CPU
{083764E8-4C34-43FB-A468-F80CE0ADE10A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{083764E8-4C34-43FB-A468-F80CE0ADE10A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{083764E8-4C34-43FB-A468-F80CE0ADE10A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{083764E8-4C34-43FB-A468-F80CE0ADE10A}.Release|Any CPU.Build.0 = Release|Any CPU
{9AD16D72-E3F5-4A76-814C-40EBD1EE7892}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AD16D72-E3F5-4A76-814C-40EBD1EE7892}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AD16D72-E3F5-4A76-814C-40EBD1EE7892}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AD16D72-E3F5-4A76-814C-40EBD1EE7892}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9DEFC158-1225-4393-8A38-22256211D43D}
EndGlobalSection
EndGlobal
<?xml version="1.0" encoding="UTF-8"?>
<testsuite tests="11" failures="0" successes="11" skips="0" time="501.518">
<testcase name="e2e Test Suites Scenario 1 - 现有项目的回归测试 被测试环境中存在一个叫做voting的项目 可以查看环境" classname="E2E Suite" time="0.155922912"></testcase>
<testcase name="e2e Test Suites Scenario 4 - PR流程 GitLab 成功提交PR" classname="E2E Suite" time="0.422994305"></testcase>
<testcase name="e2e Test Suites Scenario 5 - 服务与构建 测试环境中存在项目voting并且其服务列表(groups)不为空 可以查询系统中的服务列表" classname="E2E Suite" time="0.070303796"></testcase>
<testcase name="e2e Test Suites Scenario 5 - 服务与构建 在初始化成功新建的项目中 能够end to end的创建、查询和更新服务" classname="E2E Suite" time="4.3299411039999995"></testcase>
<testcase name="e2e Test Suites Scenario 5 - 服务与构建 在初始化成功新建的项目中 能够更新环境" classname="E2E Suite" time="62.13986061"></testcase>
<testcase name="e2e Test Suites Scenario 6 - 工作流场景 能够获取最新的工作流任务" classname="E2E Suite" time="0.140571959"></testcase>
<testcase name="e2e Test Suites Scenario 6 - 工作流场景 在初始化成功新建的项目中 能够end to end创建、验证工作流、获取交付版本信息" classname="E2E Suite" time="13.371772149"></testcase>
<testcase name="e2e Test Suites Scenario 1 - 现有项目的回归测试 被测试环境中存在一个叫做voting的项目 可以查看Voting项目下的工作流信息和触发工作流任务" classname="E2E Suite" time="364.026861554"></testcase>
<testcase name="e2e Test Suites Scenario 1 - 现有项目的回归测试 被测试环境中存在一个叫做voting的项目 可以查看Voting项目信息" classname="E2E Suite" time="0.136967508"></testcase>
<testcase name="e2e Test Suites Scenario 2 - onboarding的核心流程 经过系统的onboarding流程,自动生成了环境和一些工作流 成功的自动创建了环境和工作流,工作流可以被触发" classname="E2E Suite" time="70.83081332"></testcase>
<testcase name="e2e Test Suites Scenario 7 - 集成环境验证场景 测试环境中存在项目voting, 在项目voting中 end to end的创建、更新和删除集成环境" classname="E2E Suite" time="196.670293439"></testcase>
</testsuite>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<testsuite tests="13" failures="0" skips="0" time="65.473">
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 项目测试集 新增项目,详情返回校验成功" classname="API Test Suite" time="0.256508758"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 项目测试集 已存项目,获取详情校验成功" classname="API Test Suite" time="0.05056192"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 项目测试集 删除项目,返回校验成功" classname="API Test Suite" time="0.12074505"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 服务测试集:新增、查看、删除 新增服务,详情返回校验成功" classname="API Test Suite" time="0.117066632"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 服务测试集:新增、查看、删除 已存服务,获取详情校验成功" classname="API Test Suite" time="0.034139487"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 服务测试集:新增、查看、删除 删除服务,返回校验成功" classname="API Test Suite" time="0.101714178"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 集成环境测试集:新增、查看、更新、删除 新增环境,详情返回校验成功" classname="API Test Suite" time="0.123724897"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 集成环境测试集:新增、查看、更新、删除 已存环境,获取详情校验成功" classname="API Test Suite" time="0.045644415"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 集成环境测试集:新增、查看、更新、删除 删除环境,返回校验成功" classname="API Test Suite" time="0.251620057"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 工作流测试集:新建、查看、执行、删除、webhook 触发 新建工作流,详情返回校验成功" classname="API Test Suite" time="0.059631784"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 工作流测试集:新建、查看、执行、删除、webhook 触发 查看已存在的工作流,获取详情校验成功" classname="API Test Suite" time="0.040337097"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 工作流测试集:新建、查看、执行、删除、webhook 触发 删除工作流,返回校验成功" classname="API Test Suite" time="0.126307588"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 清理资源 删除项目" classname="API Test Suite" time="0.0430006"></testcase>
</testsuite>
\ No newline at end of file
<?xml version="1.0" ?>
<testsuite errors="1" failures="0" name="cases.promotionAPI.test_activity.TestQueryActivity-20190417141546" tests="1" time="1.237">
<testcase classname="cases.promotionAPI.test_activity.TestQueryActivity" name="test001_searchNewAddActivity" time="1.237">
<error message="1 != 2" type="AssertionError">
<![CDATA[Traceback (most recent call last):
File "D:\FM\Auto\AutoCode\cases\promotionAPI\test_activity.py", line 43, in test001_searchNewAddActivity
self.assertEqual(sql_result[0][12], 2)
AssertionError: 1 != 2
]]> </error>
</testcase>
<system-out>
<![CDATA[call {"ver": 1, "activityDesc": "满100减10元优惠券", "activityName": "满100减10元优惠券", "activityStatus": 1, "activityType": 2, "isShared": 0, "cycles": [{"cycleType": 1, "cycleValue": 0}], "auditStatus": 1, "sendGoods": [{"goodsId": "bbb33333", "goodsName": "半熟蛋糕珍珠奶茶中杯", "sendNumber": 1}, {"goodsId": "bbb4444", "goodsName": "七分熟蛋糕珍珠奶茶中杯", "sendNumber": 1}], "sendCoupons": [{"activityCode": "123456", "activityName": "满100减10元", "activityType": 1, "sendNumber": 2}], "benefits": [{"benefitName": "满100减10", "benefitSeq": 1, "discountAmount": 1000, "thresholdAmount": 10000, "benefitRebate": 0.8, "benefitNumber": 10, "exchangeGoods": [{"goodsId": "a00001", "goodsName": "A商品", "nowPrice": 1000, "quantityLimit": 2, "sendNumber": 1}]}, {"benefitName": "满200减20", "benefitSeq": 1, "discountAmount": 2000, "thresholdAmount": 20000, "benefitRebate": 0.8, "benefitNumber": 10, "exchangeGoods": [{"goodsId": "a00001", "goodsName": "A商品", "nowPrice": 1000, "quantityLimit": 2, "sendNumber": 1}]}], "couponType": 1, "discountTimes": 10, "startTime": "2019-3-14 00:00:00", "endTime": "2020-12-31 23:59:59", "partnerCode": 2252, "stores": [{"storeId": "9999"}, {"storeId": "1030"}], "totalTimes": 1000, "validityPeriodEnd": "2020-12-31 23:59:59", "validityPeriodStart": "2018-12-31 23:59:59", "goods": [{"goodsId": "6970928490402", "goodsName": "快乐奶茶", "originalPrice": 30, "quantityLimit": 0, "nowPrice": 10, "isCanUse": 1, "isShared": 1, "number": 0, "rebate": 80, "shareActivityCodes": [], "shareActivityTypes": []}]}
{'ver': 1, 'activityDesc': '满100减10元优惠券', 'activityName': '满100减10元优惠券', 'activityStatus': 1, 'activityType': 2, 'isShared': 0, 'cycles': [{'cycleType': 1, 'cycleValue': 0}], 'auditStatus': 1, 'sendGoods': [{'goodsId': 'bbb33333', 'goodsName': '半熟蛋糕珍珠奶茶中杯', 'sendNumber': 1}, {'goodsId': 'bbb4444', 'goodsName': '七分熟蛋糕珍珠奶茶中杯', 'sendNumber': 1}], 'sendCoupons': [{'activityCode': '123456', 'activityName': '满100减10元', 'activityType': 1, 'sendNumber': 2}], 'benefits': [{'benefitName': '满100减10', 'benefitSeq': 1, 'discountAmount': 1000, 'thresholdAmount': 10000, 'benefitRebate': 0.8, 'benefitNumber': 10, 'exchangeGoods': [{'goodsId': 'a00001', 'goodsName': 'A商品', 'nowPrice': 1000, 'quantityLimit': 2, 'sendNumber': 1}]}, {'benefitName': '满200减20', 'benefitSeq': 1, 'discountAmount': 2000, 'thresholdAmount': 20000, 'benefitRebate': 0.8, 'benefitNumber': 10, 'exchangeGoods': [{'goodsId': 'a00001', 'goodsName': 'A商品', 'nowPrice': 1000, 'quantityLimit': 2, 'sendNumber': 1}]}], 'couponType': 1, 'discountTimes': 10, 'startTime': '2019-3-14 00:00:00', 'endTime': '2020-12-31 23:59:59', 'partnerCode': 2252, 'stores': [{'storeId': '9999'}, {'storeId': '1030'}], 'totalTimes': 1000, 'validityPeriodEnd': '2020-12-31 23:59:59', 'validityPeriodStart': '2018-12-31 23:59:59', 'goods': [{'goodsId': '6970928490402', 'goodsName': '快乐奶茶', 'originalPrice': 30, 'quantityLimit': 0, 'nowPrice': 10, 'isCanUse': 1, 'isShared': 1, 'number': 0, 'rebate': 80, 'shareActivityCodes': [], 'shareActivityTypes': []}]} {'msg': '成功', 'result': 'DPTJ2019041714140209298', 'statusCode': 100, 'ver': 1}
call {"partnerCode": 2252, "storeId": "9999", "ver": 1, "activityType": 2, "area": "", "activityTypes": "", "channel": "", "activityCodes": ["DPTJ2019041714140209298"], "memberLevel": "", "appId": "", "userId": "", "application": "", "isShowStore": "", "isShowTime": "", "isShowGoods": "", "isShowBenefit": "", "isShowSendGoods": "", "isShowSendCoupon": "", "isShowCycles": ""}
]]> </system-out>
<system-err>
<![CDATA[D:\Program Files\Python37\lib\site-packages\urllib3\connectionpool.py:858: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
D:\Program Files\Python37\lib\site-packages\urllib3\connectionpool.py:858: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
]]> </system-err>
</testsuite>
<?xml version="1.0" encoding="UTF-8"?>
<testsuite tests="11" failures="0" successes="11" skips="0" time="501.518">
<testcase name="e2e Test Suites Scenario 1 - 现有项目的回归测试 被测试环境中存在一个叫做voting的项目 可以查看环境" classname="E2E Suite" time="0.155922912"></testcase>
<testcase name="e2e Test Suites Scenario 4 - PR流程 GitLab 成功提交PR" classname="E2E Suite" time="0.422994305"></testcase>
<testcase name="e2e Test Suites Scenario 5 - 服务与构建 测试环境中存在项目voting并且其服务列表(groups)不为空 可以查询系统中的服务列表" classname="E2E Suite" time="0.070303796"></testcase>
<testcase name="e2e Test Suites Scenario 5 - 服务与构建 在初始化成功新建的项目中 能够end to end的创建、查询和更新服务" classname="E2E Suite" time="4.3299411039999995"></testcase>
<testcase name="e2e Test Suites Scenario 5 - 服务与构建 在初始化成功新建的项目中 能够更新环境" classname="E2E Suite" time="62.13986061"></testcase>
<testcase name="e2e Test Suites Scenario 6 - 工作流场景 能够获取最新的工作流任务" classname="E2E Suite" time="0.140571959"></testcase>
<testcase name="e2e Test Suites Scenario 6 - 工作流场景 在初始化成功新建的项目中 能够end to end创建、验证工作流、获取交付版本信息" classname="E2E Suite" time="13.371772149"></testcase>
<testcase name="e2e Test Suites Scenario 1 - 现有项目的回归测试 被测试环境中存在一个叫做voting的项目 可以查看Voting项目下的工作流信息和触发工作流任务" classname="E2E Suite" time="364.026861554"></testcase>
<testcase name="e2e Test Suites Scenario 1 - 现有项目的回归测试 被测试环境中存在一个叫做voting的项目 可以查看Voting项目信息" classname="E2E Suite" time="0.136967508"></testcase>
<testcase name="e2e Test Suites Scenario 2 - onboarding的核心流程 经过系统的onboarding流程,自动生成了环境和一些工作流 成功的自动创建了环境和工作流,工作流可以被触发" classname="E2E Suite" time="70.83081332"></testcase>
<testcase name="e2e Test Suites Scenario 7 - 集成环境验证场景 测试环境中存在项目voting, 在项目voting中 end to end的创建、更新和删除集成环境" classname="E2E Suite" time="196.670293439"></testcase>
</testsuite>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<testsuite tests="13" failures="0" skips="0" time="65.473">
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 项目测试集 新增项目,详情返回校验成功" classname="API Test Suite" time="0.256508758"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 项目测试集 已存项目,获取详情校验成功" classname="API Test Suite" time="0.05056192"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 项目测试集 删除项目,返回校验成功" classname="API Test Suite" time="0.12074505"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 服务测试集:新增、查看、删除 新增服务,详情返回校验成功" classname="API Test Suite" time="0.117066632"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 服务测试集:新增、查看、删除 已存服务,获取详情校验成功" classname="API Test Suite" time="0.034139487"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 服务测试集:新增、查看、删除 删除服务,返回校验成功" classname="API Test Suite" time="0.101714178"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 集成环境测试集:新增、查看、更新、删除 新增环境,详情返回校验成功" classname="API Test Suite" time="0.123724897"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 集成环境测试集:新增、查看、更新、删除 已存环境,获取详情校验成功" classname="API Test Suite" time="0.045644415"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 集成环境测试集:新增、查看、更新、删除 删除环境,返回校验成功" classname="API Test Suite" time="0.251620057"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 工作流测试集:新建、查看、执行、删除、webhook 触发 新建工作流,详情返回校验成功" classname="API Test Suite" time="0.059631784"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 工作流测试集:新建、查看、执行、删除、webhook 触发 查看已存在的工作流,获取详情校验成功" classname="API Test Suite" time="0.040337097"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 工作流测试集:新建、查看、执行、删除、webhook 触发 删除工作流,返回校验成功" classname="API Test Suite" time="0.126307588"></testcase>
<testcase name="API Test Suites API 测试集:项目、服务、集成环境、工作流 清理资源 删除项目" classname="API Test Suite" time="0.0430006"></testcase>
</testsuite>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<testsuites disabled="true" errors="2" failures="0" name="测试" tests="4" time="3.398" >
<testsuite name="测试5" tests="2" disabled="true" errors="1" failures="0" hostname="" id="" package="" skipped="0" time="1.398" timestamp="1582711448" >
<properties>
<property name="" value=""/>
</properties>
<testcase name="API Test Suites API 工作流获取成功" classname="pipeline Suite" time="9.75117752" assertions="" status="">
<error message="" type="error" ></error>
</testcase>
<testcase name="API Test Suites API 工作流获取成功创建任务" classname="pipeline Suite" time="9.75117752"></testcase>
</testsuite>
<testsuite name="测试6" tests="2" disabled="true" errors="1" failures="0" hostname="" id="" package="" skipped="0" time="1.398" timestamp="1582711448" >
<properties>
<property name="" value=""/>
</properties>
<testcase name="API Test Suites API 工作流重试成功" classname="pipeline Suite" time="9.75117752" assertions="" status="">
<error message="" type="error"></error>
</testcase>
<testcase name="API Test Suites API 工作流克隆任务" classname="pipeline Suite" time="9.75117752"></testcase>
</testsuite>
</testsuites>
<?xml version="1.0" encoding="UTF-8"?>
<testsuites disabled="true" errors="" failures="2" name="测试" tests="4" time="3.398" >
<testsuite name="测试3" tests="2" disabled="true" errors="" failures="1" hostname="" id="" package="" skipped="0" time="1.398" timestamp="1582711448" >
<properties>
<property name="" value=""/>
</properties>
<testcase name="API Test Suites API 工作流获取成功" classname="pipeline Suite" time="9.75117752" assertions="" status="">
<failure message="Failed" type="" ></failure>
</testcase>
<testcase name="API Test Suites API 工作流获取成功创建任务" classname="pipeline Suite" time="9.75117752"></testcase>
</testsuite>
<testsuite name="测试4" tests="2" disabled="true" errors="" failures="1" hostname="" id="" package="" skipped="0" time="1.398" timestamp="1582711448" >
<properties>
<property name="" value=""/>
</properties>
<testcase name="API Test Suites API 工作流重试成功" classname="pipeline Suite" time="9.75117752" assertions="" status="">
<failure message="Failed" type="" ></failure>
</testcase>
<testcase name="API Test Suites API 工作流克隆任务" classname="pipeline Suite" time="9.75117752"></testcase>
</testsuite>
</testsuites>
<?xml version="1.0" encoding="UTF-8"?>
<testsuites disabled="true" errors="" failures="0" name="测试" tests="8" time="3.398" >
<testsuite name="测试1" tests="4" disabled="true" errors="" failures="0" hostname="" id="" package="" skipped="1" time="1.398" timestamp="1582711448" >
<properties>
<property name="" value=""/>
</properties>
<testcase name="API Test Suites API 工作流获取成功" classname="pipeline Suite" time="9.75117752" assertions="" status="">
<skipped/>
</testcase>
<testcase name="API Test Suites API 工作流创建任务成功" classname="pipeline Suite" time="9.75117752"></testcase>
<testcase name="API Test Suites API 工作流执行任务成功" classname="pipeline Suite" time="10.043716176"></testcase>
<testcase name="API Test Suites API 工作流取消任务成功" classname="pipeline Suite" time="0.262019138"></testcase>
</testsuite>
<testsuite name="测试2" tests="4" disabled="true" errors="" failures="0" hostname="" id="" package="" skipped="1" time="1.398" timestamp="1582711448" >
<properties>
<property name="" value=""/>
</properties>
<testcase name="API Test Suites API 工作流重试任务成功" classname="pipeline Suite" time="9.75117752" assertions="" status="">
<skipped/>
</testcase>
<testcase name="API Test Suites API 工作流取消重试任务成功" classname="pipeline Suite" time="9.75117752"></testcase>
<testcase name="API Test Suites API 工作流克隆任务成功" classname="pipeline Suite" time="10.043716176"></testcase>
<testcase name="API Test Suites API 删除工作流成功" classname="pipeline Suite" time="0.262019138"></testcase>
</testsuite>
</testsuites>
\ No newline at end of file
FROM node:10-slim
WORKDIR /app
RUN npm install -g nodemon
COPY package*.json ./
RUN npm ci \
&& npm cache clean --force \
&& mv /app/node_modules /node_modules
COPY . .
ENV PORT 80
EXPOSE 80
CMD ["node", "server.js"]
version: '2'
services:
sut:
build: ./tests/
depends_on:
- vote
- result
- worker
networks:
- front-tier
vote:
build: ../vote/
ports: ["80"]
depends_on:
- redis
- db
networks:
- front-tier
- back-tier
result:
build: .
ports: ["80"]
depends_on:
- redis
- db
networks:
- front-tier
- back-tier
worker:
build: ../worker/
depends_on:
- redis
- db
networks:
- back-tier
redis:
image: redis:alpine
ports: ["6379"]
networks:
- back-tier
db:
image: postgres:9.4
volumes:
- "db-data:/var/lib/postgresql/data"
networks:
- back-tier
volumes:
db-data:
networks:
front-tier:
back-tier:
FROM microsoft/dotnet:2.1-sdk-nanoserver-sac2016 as builder
WORKDIR /Result
COPY Result/Result.csproj .
RUN dotnet restore
COPY /Result .
RUN dotnet publish -c Release -o /out Result.csproj
# app image
FROM microsoft/dotnet:2.1-aspnetcore-runtime-nanoserver-sac2016
WORKDIR /app
ENTRYPOINT ["dotnet", "Result.dll"]
COPY --from=builder /out .
\ No newline at end of file
FROM microsoft/dotnet:2.1-sdk-nanoserver-1809 as builder
WORKDIR /Result
COPY Result/Result.csproj .
RUN dotnet restore
COPY /Result .
RUN dotnet publish -c Release -o /out Result.csproj
# app image
FROM microsoft/dotnet:2.1-aspnetcore-runtime-nanoserver-1809
WORKDIR /app
ENTRYPOINT ["dotnet", "Result.dll"]
COPY --from=builder /out .
\ No newline at end of file
using Result.Models;
namespace Result.Data
{
public interface IResultData
{
ResultsModel GetResults();
}
}
using System.Linq;
using Dapper;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MySql.Data.MySqlClient;
using Result.Models;
namespace Result.Data
{
public class MySqlResultData : IResultData
{
private readonly string _connectionString;
private readonly ILogger _logger;
public MySqlResultData(IConfiguration config, ILogger<MySqlResultData> logger)
{
_connectionString = config.GetConnectionString("ResultData");
_logger = logger;
}
public ResultsModel GetResults()
{
var model = new ResultsModel();
using (var connection = new MySqlConnection(_connectionString))
{
var results = connection.Query("SELECT vote, COUNT(id) AS count FROM votes GROUP BY vote ORDER BY vote");
if (results.Any(x => x.vote == "a"))
{
model.OptionA = (int) results.First(x => x.vote == "a").count;
}
if (results.Any(x => x.vote == "b"))
{
model.OptionB = (int) results.First(x => x.vote == "b").count;
}
model.VoteCount = model.OptionA + model.OptionB;
}
return model;
}
}
}
using Microsoft.AspNetCore.SignalR;
namespace Result.Hubs
{
public class ResultsHub : Hub
{
//no public methods, only used for push from PublishRTesultsTimer
}
}
namespace Result.Models
{
public class ResultsModel
{
public int OptionA { get; set; }
public int OptionB { get; set; }
public int VoteCount { get; set; }
}
}
@page
@model Result.Pages.IndexModel
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>@Model.OptionA vs @Model.OptionB -- Result</title>
<base href="/index.html">
<meta name="viewport" content="width=device-width, initial-scale = 1.0">
<meta name="keywords" content="docker-compose, docker, stack">
<meta name="author" content="Docker">
<link rel='stylesheet' href='~/css/site.css' />
</head>
<body>
<div id="background-stats">
<div id="background-stats-1">
</div>
<!--
-->
<div id="background-stats-2">
</div>
</div>
<div id="content-container">
<div id="content-container-center">
<div id="choice">
<div class="choice resulta">
<div class="label">@Model.OptionA</div>
<div class="stat" id="optionA">50%</div>
</div>
<div class="divider"></div>
<div class="choice resultb">
<div class="label">@Model.OptionB</div>
<div class="stat" id="optionB">50%</div>
</div>
</div>
</div>
</div>
<div id="result">
<span id="totalVotes">No votes yet</span>
</div>
<script src="~/lib/signalr/dist/browser/signalr.min.js"></script>
<script src="~/js/results.js"></script>
</body>
</html>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
namespace Result.Pages
{
public class IndexModel : PageModel
{
private string _optionA;
private string _optionB;
protected readonly IConfiguration _configuration;
public string OptionA { get; private set; }
public string OptionB { get; private set; }
public IndexModel(IConfiguration configuration)
{
_configuration = configuration;
_optionA = _configuration.GetValue<string>("Voting:OptionA");
_optionB = _configuration.GetValue<string>("Voting:OptionB");
}
public void OnGet()
{
OptionA = _optionA;
OptionB = _optionB;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Result
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:56785",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Result": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="1.50.5" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="MySql.Data" Version="8.0.12" />
</ItemGroup>
</Project>
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Result.Data;
using Result.Hubs;
using Result.Timers;
namespace Result
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
services.AddTransient<IResultData, MySqlResultData>()
.AddSingleton<PublishResultsTimer>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseSignalR(routes =>
{
routes.MapHub<ResultsHub>("/resultsHub");
});
app.UseMvc();
var timer = app.ApplicationServices.GetService<PublishResultsTimer>();
timer.Start();
}
}
}
using System.Timers;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration;
using Result.Data;
using Result.Hubs;
namespace Result.Timers
{
public class PublishResultsTimer
{
private readonly IHubContext<ResultsHub> _hubContext;
private readonly IResultData _resultData;
private readonly Timer _timer;
public PublishResultsTimer(IHubContext<ResultsHub> hubContext, IResultData resultData, IConfiguration configuration)
{
_hubContext = hubContext;
_resultData = resultData;
var publishMilliseconds = configuration.GetValue<int>("ResultsTimer:PublishMilliseconds");
_timer = new Timer(publishMilliseconds)
{
Enabled = false
};
_timer.Elapsed += PublishResults;
}
public void Start()
{
if (!_timer.Enabled)
{
_timer.Start();
}
}
private void PublishResults(object sender, ElapsedEventArgs e)
{
var model = _resultData.GetResults();
_hubContext.Clients.All.SendAsync("UpdateResults", model);
}
}
}
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
{
"Voting": {
"OptionA": "Cats",
"OptionB": "Dogs"
},
"ResultsTimer": {
"PublishMilliseconds": 2500
},
"ConnectionStrings": {
"ResultData": "Server=mysql;Port=4000;Database=votes;User=root;SslMode=None"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
{
"version": "1.0",
"defaultProvider": "unpkg",
"libraries": [
{
"library": "@aspnet/signalr@1.0.3",
"destination": "wwwroot/lib/signalr/",
"files": [
"dist/browser/signalr.js",
"dist/browser/signalr.min.js"
]
}
]
}
\ No newline at end of file
@import url(//fonts.lug.ustc.edu.cn/css?family=Open+Sans:400,700,600);
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: 'Open Sans';
}
body {
opacity: 0;
transition: all 1s linear;
}
.divider {
height: 150px;
width: 2px;
background-color: #C0C9CE;
position: relative;
top: 50%;
float: left;
transform: translateY(-50%);
}
#background-stats-1 {
background-color: #2196f3;
}
#background-stats-2 {
background-color: #00cbca;
}
#content-container {
z-index: 2;
position: relative;
margin: 0 auto;
display: table;
padding: 10px;
max-width: 940px;
height: 100%;
}
#content-container-center {
display: table-cell;
text-align: center;
vertical-align: middle;
}
#result {
z-index: 3;
position: absolute;
bottom: 40px;
right: 20px;
color: #fff;
opacity: 0.5;
font-size: 45px;
font-weight: 600;
}
#choice {
transition: all 300ms linear;
line-height: 1.3em;
background: #fff;
box-shadow: 10px 0 0 #fff, -10px 0 0 #fff;
vertical-align: middle;
font-size: 40px;
font-weight: 600;
width: 450px;
height: 200px;
}
#choice a {
text-decoration: none;
}
#choice a:hover, #choice a:focus {
outline: 0;
text-decoration: underline;
}
#choice .choice {
width: 49%;
position: relative;
top: 50%;
transform: translateY(-50%);
text-align: left;
padding-left: 50px;
}
#choice .choice .label {
text-transform: uppercase;
}
#choice .choice.resultb {
color: #00cbca;
float: right;
}
#choice .choice.resulta {
color: #2196f3;
float: left;
}
#background-stats {
z-index: 1;
height: 100%;
width: 100%;
position: absolute;
}
#background-stats div {
transition: width 400ms ease-in-out;
display: inline-block;
margin-bottom: -4px;
width: 50%;
height: 100%;
}
body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}}
\ No newline at end of file
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/resultsHub").build();
connection.on("UpdateResults", function (results) {
document.body.style.opacity=1;
var a = parseInt(results.optionA || 0);
var b = parseInt(results.optionB || 0);
var percentages = getPercentages(a, b);
document.getElementById("optionA").innerText = percentages.a + "%";
document.getElementById("optionB").innerText = percentages.b + "%";
var totalVotes = 'No votes yet';
if (results.voteCount > 0) {
totalVotes = results.voteCount + (results.voteCount > 1 ? " votes" : " vote");
}
document.getElementById("totalVotes").innerText = totalVotes;
var bg1 = document.getElementById('background-stats-1');
var bg2 = document.getElementById('background-stats-2');
bg1.style.width = (percentages.a-0.2) + "%";
bg2.style.width = (percentages.b-0.2) + "%";
});
connection.start().catch(function (err) {
return console.error(err.toString());
});
function getPercentages(a, b) {
var result = {};
if (a + b > 0) {
result.a = Math.round(a / (a + b) * 100);
result.b = 100 - result.a;
} else {
result.a = result.b = 50;
}
return result;
}
\ No newline at end of file
此差异已折叠。
{
"name": "result",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"async": "^3.1.0",
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.4",
"express": "^4.17.1",
"method-override": "^3.0.0",
"pg": "^7.12.1",
"socket.io": "^2.2.0",
"stoppable": "^1.1.0"
}
}
var express = require('express'),
async = require('async'),
pg = require('pg'),
{ Pool } = require('pg'),
path = require('path'),
cookieParser = require('cookie-parser'),
bodyParser = require('body-parser'),
methodOverride = require('method-override'),
app = express(),
server = require('http').Server(app),
io = require('socket.io')(server);
io.set('transports', ['polling']);
var port = process.env.PORT || 4000;
io.sockets.on('connection', function (socket) {
socket.emit('message', { text : 'Welcome!' });
socket.on('subscribe', function (data) {
socket.join(data.channel);
});
});
var pool = new pg.Pool({
connectionString: 'postgres://postgres@db/postgres'
});
async.retry(
{times: 1000, interval: 1000},
function(callback) {
pool.connect(function(err, client, done) {
if (err) {
console.error("Waiting for db...");
}
callback(err, client);
});
},
function(err, client) {
if (err) {
return console.error("Giving up");
}
console.log("Connected to db");
getVotes(client);
}
);
function getVotes(client) {
client.query('SELECT vote, COUNT(id) AS count FROM votes GROUP BY vote', [], function(err, result) {
if (err) {
console.error("Error performing query: " + err);
} else {
var votes = collectVotesFromResult(result);
io.sockets.emit("scores", JSON.stringify(votes));
}
setTimeout(function() {getVotes(client) }, 1000);
});
}
function collectVotesFromResult(result) {
var votes = {a: 0, b: 0, c: 0};
result.rows.forEach(function (row) {
votes[row.vote] = parseInt(row.count);
});
return votes;
}
app.use(cookieParser());
app.use(bodyParser());
app.use(methodOverride('X-HTTP-Method-Override'));
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
next();
});
app.use(express.static(__dirname + '/views'));
app.get('/', function (req, res) {
res.sendFile(path.resolve(__dirname + '/views/index.html'));
});
server.listen(port, function () {
var port = server.address().port;
console.log('App running on port ' + port);
});
FROM wernight/phantomjs
USER root
RUN sed -i "s/deb.debian.org/mirrors.aliyun.com/g" /etc/apt/sources.list
RUN sed -i "s/security.debian.org/mirrors.aliyun.com/g" /etc/apt/sources.list
RUN apt update && apt install -y curl
# RUN apt-get update && apt-get install -qy \
# ca-certificates \
# bzip2 \
# curl \
# libfontconfig \
# --no-install-recommends
# RUN yarn config set registry https://registry.npm.taobao.org/
# RUN yarn global add --verbose phantomjs-prebuilt
ADD . /app
WORKDIR /app
CMD ["/app/tests.sh"]
var system = require('system');
var page = require('webpage').create();
var url = system.args[1];
page.onLoadFinished = function() {
setTimeout(function(){
console.log(page.content);
phantom.exit();
}, 1000);
};
page.open(url, function() {
page.evaluate(function() {
});
});
#!/bin/sh
while ! timeout 1 bash -c "echo > /dev/tcp/vote/80"; do
sleep 1
done
curl -sS -X POST --data "vote=b" http://vote > /dev/null
sleep 10
if phantomjs render.js http://result | grep -q '1 vote'; then
echo -e "\\e[42m------------"
echo -e "\\e[92mTests passed"
echo -e "\\e[42m------------"
exit 0
else
echo -e "\\e[41m------------"
echo -e "\\e[91mTests failed"
echo -e "\\e[41m------------"
exit 1
fi
此差异已折叠。
var app = angular.module('catsvsdogs', []);
var socket = io.connect({transports:['polling']});
var bg1 = document.getElementById('background-stats-1');
var bg2 = document.getElementById('background-stats-2');
var bg3 = document.getElementById('background-stats-3');
app.controller('statsCtrl', function($scope){
$scope.aPercent = 33.33;
$scope.bPercent = 33.33;
$scope.cPercent = 33.34;
var updateScores = function(){
socket.on('scores', function (json) {
data = JSON.parse(json);
var a = parseInt(data.a || 0);
var b = parseInt(data.b || 0);
var c = parseInt(data.c || 0);
var percentages = getPercentages(a, b, c);
bg1.style.width = percentages.a + "%";
bg2.style.width = percentages.b + "%";
bg3.style.width = percentages.c + "%";
$scope.$apply(function () {
$scope.aPercent = percentages.a;
$scope.bPercent = percentages.b;
$scope.cPercent = percentages.c;
$scope.total = a + b + c;
});
});
};
var init = function(){
document.body.style.opacity=1;
updateScores();
};
socket.on('message',function(data){
init();
});
});
function getPercentages(a, b, c) {
var result = {};
if (a + b + c > 0) {
result.a = Math.round(a / (a + b + c) * 100);
result.b = Math.round(b / (a + b + c) * 100);
result.c = 100 - result.a - result.b;
} else {
result.a = result.b = 33.33;
result.c = 33.34;
}
return result;
}
\ No newline at end of file
<!DOCTYPE html>
<html ng-app="catsvsdogs">
<head>
<meta charset="utf-8">
<title>Cats vs Dogs -- Result</title>
<base href="/index.html">
<meta name = "viewport" content = "width=device-width, initial-scale = 1.0">
<meta name="keywords" content="docker-compose, docker, stack">
<meta name="author" content="Docker">
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body ng-controller="statsCtrl" >
<div id="background-stats">
<div id="background-stats-1">
</div><!--
--><div id="background-stats-2">
</div><!--
--><div id="background-stats-3">
</div>
</div>
<div id="content-container">
<div id="content-container-center">
<div id="choice">
<div class="choice cats">
<div class="label">Cats</div>
<div class="stat">{{aPercent | number:1}}%</div>
</div>
<div class="divider"></div>
<div class="choice dogs">
<div class="label">Dogs</div>
<div class="stat">{{bPercent | number:1}}%</div>
</div>
<div class="divider"></div>
<div class="choice donkeys">
<div class="label">Donkeys</div>
<div class="stat">{{cPercent | number:1}}%</div>
</div>
</div>
</div>
</div>
<div id="result">
<span ng-if="total == 0">No votes yet</span>
<span ng-if="total == 1">{{total}} vote</span>
<span ng-if="total >= 2">{{total}} votes</span>
</div>
<script src="socket.io.js"></script>
<script src="angular.min.js"></script>
<script src="app.js"></script>
</body>
</html>
此差异已折叠。
@import url(//fonts.lug.ustc.edu.cn/css?family=Open+Sans:400,700,600);
*{
box-sizing:border-box;
}
html,body{
margin:0;
padding:0;
height:100%;
font-family: 'Open Sans';
}
body{
opacity:0;
transition: all 1s linear;
}
.divider{
height: 150px;
width:2px;
background-color: #C0C9CE;
position: relative;
top: 50%;
float: left;
transform: translateY(-50%);
}
#background-stats-1{
background-color: #2196f3;
}
#background-stats-2{
background-color: #00cbca;
}
#background-stats-3{
background-color: #448888;
}
#content-container{
z-index:2;
position:relative;
margin:0 auto;
display:table;
padding:10px;
max-width:940px;
height:100%;
}
#content-container-center{
display:table-cell;
text-align:center;
vertical-align:middle;
}
#result{
z-index: 3;
position: absolute;
bottom: 40px;
right: 20px;
color: #fff;
opacity: 0.5;
font-size: 45px;
font-weight: 600;
}
#choice{
transition: all 300ms linear;
line-height:1.3em;
background:#fff;
box-shadow: 10px 0 0 #fff, -10px 0 0 #fff;
vertical-align:middle;
font-size: 30px;
font-weight: 600;
width: 540px;
height: 200px;
}
#choice a{
text-decoration:none;
}
#choice a:hover, #choice a:focus{
outline:0;
text-decoration:underline;
}
#choice .choice{
width: 49%;
position: relative;
top: 50%;
transform: translateY(-50%);
text-align: left;
padding-left: 40px;
float: left;
}
#choice .choice .label{
text-transform: uppercase;
}
#choice .choice.dogs{
color: #00cbca;
width: 32%;
}
#choice .choice.cats{
color: #2196f3;
width: 32%;
}
#choice .choice.donkeys{
color: #333333;
width: 32%;
}
#background-stats{
z-index:1;
height:100%;
width:100%;
position:absolute;
}
#background-stats div{
transition: width 400ms ease-in-out;
display:inline-block;
margin-bottom:-4px;
width:50%;
height:100%;
}
# Using official python runtime base image
FROM python:3.6-alpine
# Set the application directory
WORKDIR /app
# Install our requirements.txt
ADD requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
# Copy our code from the current folder to /app inside the container
ADD . /app
# Make port 80 available for links and/or publish
EXPOSE 80
# Define our command to be run when launching the container
CMD ["gunicorn", "app:app", "-b", "0.0.0.0:80", "--log-file", "-", "--access-logfile", "-", "--workers", "4", "--keep-alive", "0"]
from flask import Flask, render_template, request, make_response, g
from redis import Redis
import os
import socket
import random
import json
option_a = os.getenv('OPTION_A', "Cats")
option_b = os.getenv('OPTION_B', "Dogs")
option_c = os.getenv('OPTION_C', "Donkeys")
hostname = socket.gethostname()
app = Flask(__name__)
def get_redis():
if not hasattr(g, 'redis'):
g.redis = Redis(host="redis", db=0, socket_timeout=5)
return g.redis
def get_voter_id(cookies):
voter_id = cookies.get('voter_id')
if not voter_id:
voter_id = hex(random.getrandbits(64))[2:-1]
return voter_id
@app.route("/", methods=['POST', 'GET'])
def hello():
voter_id = get_voter_id(request.cookies)
vote = None
if request.method == 'POST':
redis = get_redis()
vote = request.form['vote']
data = json.dumps({'voter_id': voter_id, 'vote': vote})
redis.rpush('votes', data)
resp = make_response(render_template(
'index.html',
option_a=option_a,
option_b=option_b,
option_c=option_c,
hostname=hostname,
vote=vote,
))
resp.set_cookie('voter_id', voter_id)
return resp
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug=True, threaded=True)
FROM microsoft/dotnet:2.1-sdk-nanoserver-sac2016 as builder
WORKDIR /Vote
COPY Vote/Vote.csproj .
RUN dotnet restore
COPY /Vote .
RUN dotnet publish -c Release -o /out Vote.csproj
# app image
FROM microsoft/dotnet:2.1-aspnetcore-runtime-nanoserver-sac2016
WORKDIR /app
ENTRYPOINT ["dotnet", "Vote.dll"]
COPY --from=builder /out .
\ No newline at end of file
FROM microsoft/dotnet:2.1-sdk-nanoserver-1809 as builder
WORKDIR /Vote
COPY Vote/Vote.csproj .
RUN dotnet restore
COPY /Vote .
RUN dotnet publish -c Release -o /out Vote.csproj
# app image
FROM microsoft/dotnet:2.1-aspnetcore-runtime-nanoserver-1809
WORKDIR /app
ENTRYPOINT ["dotnet", "Vote.dll"]
COPY --from=builder /out .
\ No newline at end of file
using NATS.Client;
using Vote.Messaging.Messages;
namespace Vote.Messaging
{
public interface IMessageQueue
{
IConnection CreateConnection();
void Publish<TMessage>(TMessage message) where TMessage : Message;
}
}
\ No newline at end of file
using Newtonsoft.Json;
using Vote.Messaging.Messages;
using System.Text;
namespace Vote.Messaging
{
public class MessageHelper
{
public static byte[] ToData<TMessage>(TMessage message)
where TMessage : Message
{
var json = JsonConvert.SerializeObject(message);
return Encoding.Unicode.GetBytes(json);
}
public static TMessage FromData<TMessage>(byte[] data)
where TMessage : Message
{
var json = Encoding.Unicode.GetString(data);
return (TMessage)JsonConvert.DeserializeObject<TMessage>(json);
}
}
}
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NATS.Client;
using Vote.Messaging.Messages;
namespace Vote.Messaging
{
public class MessageQueue : IMessageQueue
{
protected readonly IConfiguration _configuration;
protected readonly ILogger _logger;
public MessageQueue(IConfiguration configuration, ILogger<MessageQueue> logger)
{
_configuration = configuration;
_logger = logger;
}
public void Publish<TMessage>(TMessage message)
where TMessage : Message
{
using (var connection = CreateConnection())
{
var data = MessageHelper.ToData(message);
connection.Publish(message.Subject, data);
}
}
public IConnection CreateConnection()
{
var url = _configuration.GetValue<string>("MessageQueue:Url");
return new ConnectionFactory().CreateConnection(url);
}
}
}
using System;
namespace Vote.Messaging.Messages
{
public class VoteCastEvent : Message
{
public override string Subject { get { return MessageSubject; } }
public string VoterId {get; set;}
public string Vote {get; set; }
public static string MessageSubject = "events.vote.votecast";
}
}
\ No newline at end of file
using System;
namespace Vote.Messaging.Messages
{
public abstract class Message
{
public string CorrelationId { get; set; }
public abstract string Subject { get; }
public Message()
{
CorrelationId = Guid.NewGuid().ToString();
}
}
}
@page
@model Vote.Pages.IndexModel
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>@Model.OptionA vs @Model.OptionB!</title>
<base href="/index.html">
<meta name="viewport" content="width=device-width, initial-scale = 1.0">
<meta name="keywords" content="docker-compose, docker, stack">
<meta name="author" content="Docker DevRel team">
<link rel="stylesheet" href="~/css/site.css" />
<link rel="stylesheet" href="http://cdn.staticfile.org/font-awesome/4.0.0/css/font-awesome.min.css">
</head>
<body>
<div id="content-container">
<div id="content-container-center">
<h3>@Model.OptionA vs @Model.OptionB!</h3>
<form method="POST" id="choice" name='form'>
<button id="a" type="submit" name="vote" class="a" value="a">@Model.OptionA</button>
<button id="b" type="submit" name="vote" class="b" value="b">@Model.OptionB</button>
</form>
<div id="tip">
(Tip: you can change your vote)
</div>
<div id="hostname">
Processed by container ID @System.Environment.MachineName
</div>
</div>
</div>
<script src="~/js/jquery-1.11.1-min.js" type="text/javascript"></script>
@*<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>*@
<script>
var vote = "@Model.Vote";
if(vote == "a"){
$(".a").prop('disabled', true);
$(".a").html('@Model.OptionA <i class="fa fa-check-circle"></i>');
$(".b").css('opacity','0.5');
}
if(vote == "b"){
$(".b").prop('disabled', true);
$(".b").html('@Model.OptionB <i class="fa fa-check-circle"></i>');
$(".a").css('opacity','0.5');
}
</script>
</body>
</html>
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Vote.Messaging;
using Vote.Messaging.Messages;
namespace Vote.Pages
{
public class IndexModel : PageModel
{
private string _optionA;
private string _optionB;
protected readonly IMessageQueue _messageQueue;
protected readonly IConfiguration _configuration;
protected readonly ILogger _logger;
public IndexModel(IMessageQueue messageQueue, IConfiguration configuration, ILogger<IndexModel> logger)
{
_messageQueue = messageQueue;
_configuration = configuration;
_logger = logger;
_optionA = _configuration.GetValue<string>("Voting:OptionA");
_optionB = _configuration.GetValue<string>("Voting:OptionB");
}
public string OptionA { get; private set; }
public string OptionB { get; private set; }
[BindProperty]
public string Vote { get; private set; }
private string _voterId
{
get { return TempData.Peek("VoterId") as string; }
set { TempData["VoterId"] = value; }
}
public void OnGet()
{
OptionA = _optionA;
OptionB = _optionB;
}
public IActionResult OnPost(string vote)
{
Vote = vote;
OptionA = _optionA;
OptionB = _optionB;
if (_configuration.GetValue<bool>("MessageQueue:Enabled"))
{
PublishVote(vote);
}
return Page();
}
private void PublishVote(string vote)
{
if (string.IsNullOrEmpty(_voterId))
{
_voterId = Guid.NewGuid().ToString();
}
var message = new VoteCastEvent
{
VoterId = _voterId,
Vote = vote
};
_messageQueue.Publish(message);
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Vote
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:6116",
"sslPort": 44316
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Vote": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册