diff --git a/.lycheeignore b/.lycheeignore index eb7ce4e9a..3a74192d4 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -6,6 +6,7 @@ https://fonts.gstatic.com/ .*foo.* .*bar.* .*xxx.* +.*changeme.* .*example.* .*YOUR_.* https://id.atlassian.net diff --git a/cmd/devstream/develop.go b/cmd/devstream/develop.go index 7c978872c..95aeaa5be 100644 --- a/cmd/devstream/develop.go +++ b/cmd/devstream/develop.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/devstream-io/devstream/internal/pkg/develop" + "github.com/devstream-io/devstream/pkg/util/cli" "github.com/devstream-io/devstream/pkg/util/log" ) @@ -12,6 +13,11 @@ var ( all bool ) +var validatePluginFlagNames = []string{ + "name", + "all", +} + var developCMD = &cobra.Command{ Use: "develop", Short: "Develop is used for develop a new plugin", @@ -33,7 +39,8 @@ var developValidatePluginCMD = &cobra.Command{ Examples: dtm develop validate-plugin --name=YOUR-PLUGIN-NAME, dtm develop validate-plugin --all`, - Run: developValidateCMDFunc, + Run: developValidateCMDFunc, + PreRun: cli.BindPFlags(validatePluginFlagNames), } func developCreateCMDFunc(cmd *cobra.Command, args []string) { diff --git a/cmd/plugin/devlake-config/main.go b/cmd/plugin/devlake-config/main.go new file mode 100644 index 000000000..b976651ba --- /dev/null +++ b/cmd/plugin/devlake-config/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/devstream-io/devstream/internal/pkg/plugin/devlakeconfig" + "github.com/devstream-io/devstream/pkg/util/log" +) + +// NAME is the name of this DevStream plugin. +const NAME = "devlake-config" + +// Plugin is the type used by DevStream core. It's a string. +type Plugin string + +// Create implements the create of devlake-config. +func (p Plugin) Create(options map[string]interface{}) (map[string]interface{}, error) { + return devlakeconfig.Create(options) +} + +// Update implements the update of devlake-config. +func (p Plugin) Update(options map[string]interface{}) (map[string]interface{}, error) { + return devlakeconfig.Update(options) +} + +// Delete implements the delete of devlake-config. +func (p Plugin) Delete(options map[string]interface{}) (bool, error) { + return devlakeconfig.Delete(options) +} + +// Read implements the read of devlake-config. +func (p Plugin) Read(options map[string]interface{}) (map[string]interface{}, error) { + return devlakeconfig.Read(options) +} + +// DevStreamPlugin is the exported variable used by the DevStream core. +var DevStreamPlugin Plugin + +func main() { + log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) +} diff --git a/docs/best-practices/gitops.md b/docs/best-practices/gitops.md index 7be2f4481..dd2ad8577 100644 --- a/docs/best-practices/gitops.md +++ b/docs/best-practices/gitops.md @@ -32,18 +32,24 @@ Note: These dependencies are optional; you can use dependency to make sure a cer ## 1 Download DevStream (`dtm`) -Download the appropriate `dtm` version for your platform from [DevStream Releases](https://github.com/devstream-io/devstream/releases). +In your working directory, run: -> Remember to rename the binary file to `dtm` so that it's easier to use. For example: `mv dtm-darwin-arm64 dtm`. +```shell +sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/install/download.sh)" +``` + +This will download the corresponding `dtm` binary to your working directory according to your OS and chip architecture, and grant the binary execution permission. -> Once downloaded, you can run the binary from anywhere. Ideally, you want to put it in a place that is in your PATH (e.g., `/usr/local/bin`). +> Optional: you can then move `dtm` to a place which is in your PATH. For example: `mv dtm /usr/local/bin/`. + +_For more details on how to install, see [install dtm](../install.md)._ ## 2 Prepare the Config File -Download the `gitops.yaml` to your working directory: +Run the following command to generate a template configuration file for gitops `gitops.yaml`. -```bash -curl -o gitops.yaml https://raw.githubusercontent.com/devstream-io/devstream/main/examples/gitops.yaml +```shell +./dtm show config -t gitops > gitops.yaml ``` Then modify the `gitops.yaml` file accordingly. diff --git a/docs/best-practices/gitops.zh.md b/docs/best-practices/gitops.zh.md index a4be618c5..a283e1283 100644 --- a/docs/best-practices/gitops.zh.md +++ b/docs/best-practices/gitops.zh.md @@ -8,11 +8,11 @@ ## 所需插件 -1. [repo-scaffolding](../plugins/repo-scaffolding.md) -2. [jira-github](../plugins/jira-github-integ.md) -3. [githubactions-golang](../plugins/githubactions-golang.md) -4. [argocd](../plugins/argocd.md) -5. [argocdapp](../plugins/argocdapp.md) +1. [repo-scaffolding](../plugins/repo-scaffolding.zh.md) +2. [jira-github](../plugins/jira-github-integ.zh.md) +3. [githubactions-golang](../plugins/githubactions-golang.zh.md) +4. [argocd](../plugins/argocd.zh.md) +5. [argocdapp](../plugins/argocdapp.zh.md) 这些插件的依赖关系如下(`a -> b`意味着`a依赖b`): @@ -22,23 +22,29 @@ **注意**:依赖并不是必须指定的,我们可以用依赖确保某个工具可以先于另外一个工具安装。我们应该根据实际的使用场景来使用`dependsOn`。 -## 1 下载DevStream(`dtm`) +## 1 下载 DevStream(`dtm`) -在[DevStream Releases](https://github.com/devstream-io/devstream/releases)页面下载适合你操作系统和CPU架构的`dtm`。 +进入你的工作目录,运行: -> 将二进制文件改名为`dtm`,以便易于使用。例如,执行:`mv dtm-drawin-arm64 dtm`。 +```shell +sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/install/download.sh)" +``` + +这个命令会根据你的操作系统和芯片架构下载对应的 `dtm` 二进制文件到你的工作目录中,并赋予二进制文件执行权限。 + +> 可选:建议你将 dtm 移动到包含于 PATH 的目录下,比如 `mv dtm /usr/local/bin/`。 -> 下载之后,你可以在任意地方执行这个二进制文件。你可以将它加入到你的PATH中(例如`/usr/local/bin`)。 +_更多安装方式详见[安装dtm](../install.zh.md)。_ ## 2 准备配置文件 -将`gitops.yaml`下载到你的工作目录下: +运行以下命令来生成 gitops 的模板配置文件 `gitops.yaml` 。 ```shell -curl -o gitops.yaml https://raw.githubusercontent.com/devstream-io/devstream/main/examples/gitops.yaml +./dtm show config -t gitops > gitops.yaml ``` -然后对`gitops.yaml`文件做相应的修改。 +然后对 `gitops.yaml` 文件做相应的修改。 配置文件中用到的变量的解释和示例值如下: diff --git a/docs/commands/install.md b/docs/commands/install.md deleted file mode 100644 index d3ad2216d..000000000 --- a/docs/commands/install.md +++ /dev/null @@ -1,28 +0,0 @@ -# Install - -## 1 Install dtm binary with curl - -In your working directory, run: - -```shell -sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/install/download.sh)" -``` - -This will download the `dtm` binary to your working directory, and grant the binary execution permission. - -> Optional: you can then move `dtm` to a place which is in your PATH. For example: `mv dtm /usr/local/bin/`. - -## 2 Install with [asdf](https://asdf-vm.com/) - -```shell -# Plugin -asdf plugin add dtm -# Show all installable versions -asdf list-all dtm -# Install specific version -asdf install dtm latest -# Set a version globally (on your ~/.tool-versions file) -asdf global dtm latest -# Now dtm commands are available -dtm --help -``` diff --git a/docs/commands/install.zh.md b/docs/commands/install.zh.md deleted file mode 100644 index 89256f051..000000000 --- a/docs/commands/install.zh.md +++ /dev/null @@ -1,28 +0,0 @@ -# 安装 - -## 1 用 curl 安装 - -进入你的工作目录,运行: - -```shell -sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/install/download.sh)" -``` - -这个命令会下载 `dtm` 二进制文件到你的工作目录中,并赋予二进制文件执行权限。 - -> 可选:建议你将 dtm 移动到包含于 PATH 的目录下,比如 `mv dtm /usr/local/bin/`。 - -## 2 用 [asdf](https://asdf-vm.com/) 安装 - -```shell -# Plugin -asdf plugin add dtm -# Show all installable versions -asdf list-all dtm -# Install specific version -asdf install dtm latest -# Set a version globally (on your ~/.tool-versions file) -asdf global dtm latest -# Now dtm commands are available -dtm --help -``` diff --git a/docs/development/commit-messages.zh.md b/docs/development/commit-messages.zh.md index 073992d5c..c60187dcb 100644 --- a/docs/development/commit-messages.zh.md +++ b/docs/development/commit-messages.zh.md @@ -1,5 +1,5 @@ # Commit 信息 -我们尽最大的努力遵守[conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary)规范。 +我们尽最大的努力遵守[conventional commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/#概述)规范。 TL;DR:提交信息应当结构如下: diff --git a/docs/development/creating-a-plugin.zh.md b/docs/development/creating-a-plugin.zh.md index 8d076e206..ecb15a772 100644 --- a/docs/development/creating-a-plugin.zh.md +++ b/docs/development/creating-a-plugin.zh.md @@ -24,7 +24,7 @@ 请在这里编写插件的主要逻辑。 -可以查看我们的[Standard Go Project Layout](project-layout.md)文件,了解关于项目布局的详细说明。 +可以查看我们的[Standard Go Project Layout](project-layout.zh.md)文件,了解关于项目布局的详细说明。 ## 2 接口 diff --git a/docs/development/project-layout.zh.md b/docs/development/project-layout.zh.md index 011dbb387..e1f54406d 100644 --- a/docs/development/project-layout.zh.md +++ b/docs/development/project-layout.zh.md @@ -1,6 +1,6 @@ # 项目组织结构 -参见 [`标准 Go 项目布局`](https://github.com/golang-standards/project-layout) 以了解更多背景信息。 +参见 [`标准 Go 项目布局`](https://github.com/golang-standards/project-layout/blob/master/README_zh.md) 以了解更多背景信息。 更多关于命名、包的组织,以及其他代码结构的建议如下: diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 000000000..97a28f0e9 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,51 @@ +# Installation + +## 0 Currently supported operating systems and chip architectures + +* Darwin/arm64 +* Darwin/amd64 +* Linux/amd64 + +## 1 Install dtm binary with script + +In your working directory, run: + +```shell +sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/install/download.sh)" +``` + +This will download the corresponding `dtm` binary to your working directory according to your OS and chip architecture, and grant the binary execution permission. + +> Optional: you can then move `dtm` to a place which is in your PATH. For example: `mv dtm /usr/local/bin/`. + +## 2 Install with [asdf](https://asdf-vm.com/) + +```shell +# Plugin +asdf plugin add dtm +# Show all installable versions +asdf list-all dtm +# Install specific version +asdf install dtm latest +# Set a version globally (on your ~/.tool-versions file) +asdf global dtm latest +# Now dtm commands are available +dtm --help +``` + +## 3 Download manually from the Release page + +You could find the latest version of `dtm` on the [Release](https://github.com/devstream-io/devstream/releases/) page and click Download. +Note that there are multiple versions of `dtm` available, so you will need to choose the correct version for your operating system and chip architecture. Once downloaded locally, you can choose to rename it, move it to the directory containing `$PATH` and give it executable permissions, for example, on Linux you can do this by running the following command. + +```shell +mv dtm-linux-amd64 /usr/local/bin/dtm +chmod +x dtm +``` + +Then you can verify that the permissions and version of dtm are correct with the following command. + +```shell +$ dtm version +0.9.1 +``` \ No newline at end of file diff --git a/docs/install.zh.md b/docs/install.zh.md new file mode 100644 index 000000000..c78c5958d --- /dev/null +++ b/docs/install.zh.md @@ -0,0 +1,51 @@ +# 安装 + +## 0 当前支持的操作系统与芯片架构 + +* Darwin/arm64 +* Darwin/amd64 +* Linux/amd64 + +## 1 用脚本安装 + +进入你的工作目录,运行: + +```shell +sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/install/download.sh)" +``` + +这个命令会根据你的操作系统和芯片架构下载对应的 `dtm` 二进制文件到你的工作目录中,并赋予二进制文件执行权限。 + +> 可选:建议你将 dtm 移动到包含于 PATH 的目录下,比如 `mv dtm /usr/local/bin/`。 + +## 2 用 [asdf](https://asdf-vm.com/) 安装 + +```shell +# Plugin +asdf plugin add dtm +# Show all installable versions +asdf list-all dtm +# Install specific version +asdf install dtm latest +# Set a version globally (on your ~/.tool-versions file) +asdf global dtm latest +# Now dtm commands are available +dtm --help +``` + +## 3 从 Release 页面手动下载 + +在 [Release](https://github.com/devstream-io/devstream/releases/) 页面找到当前最新版本 `dtm`,然后点击下载。 +需要注意的是当前 `dtm` 提供了多个版本,你需要根据操作系统和芯片架构选择自己需要的正确版本。下载到本地后,你可以选择将其重命名,移入包含在"$PATH"的目录里并赋予其可执行权限,比如在 Linux 上你可以执行如下命令完成这些操作: + +```shell +mv dtm-linux-amd64 /usr/local/bin/dtm +chmod +x dtm +``` + +接着你可以通过如下命令验证 dtm 的权限以及版本等是否正确: + +```shell +$ dtm version +0.9.1 +``` \ No newline at end of file diff --git a/docs/plugins/argocd.md b/docs/plugins/argocd.md index 7150c437f..b739f3a12 100644 --- a/docs/plugins/argocd.md +++ b/docs/plugins/argocd.md @@ -4,6 +4,10 @@ This plugin installs [ArgoCD](https://argoproj.github.io/cd/) in an existing Kub ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "argocd.yaml" ``` diff --git a/docs/plugins/argocdapp.md b/docs/plugins/argocdapp.md index 872a03113..8c1ee6930 100644 --- a/docs/plugins/argocdapp.md +++ b/docs/plugins/argocdapp.md @@ -13,6 +13,10 @@ This plugin creates an [ArgoCD Application](https://argo-cd.readthedocs.io/en/st ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "argocdapp.yaml" ``` diff --git a/docs/plugins/artifactory.md b/docs/plugins/artifactory.md index 9f3e7e871..e6d6c2d57 100644 --- a/docs/plugins/artifactory.md +++ b/docs/plugins/artifactory.md @@ -40,6 +40,10 @@ This plugin support`Ingress`, `ClusterIP`, `NodePort` and `LoadBalancer` , You c ### Config +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "artifactory.yaml" ``` diff --git a/docs/plugins/artifactory.zh.md b/docs/plugins/artifactory.zh.md index 2b3a97a14..9884a92ec 100644 --- a/docs/plugins/artifactory.zh.md +++ b/docs/plugins/artifactory.zh.md @@ -40,6 +40,10 @@ valuesYaml: | ### 配置 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ```yaml --8<-- "artifactory.yaml" ``` diff --git a/docs/plugins/ci-generic.md b/docs/plugins/ci-generic.md index e04ed0144..51438a361 100644 --- a/docs/plugins/ci-generic.md +++ b/docs/plugins/ci-generic.md @@ -1,9 +1,13 @@ -# ci-generic plugin +# ci-generic Plugin This plugin installs ci script in GitLib/GitHub repo from local or remote url. ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ``` yaml --8<-- "ci-generic.yaml" ``` diff --git a/docs/plugins/ci-generic.zh.md b/docs/plugins/ci-generic.zh.md index 45ffb9df2..dd3d9b210 100644 --- a/docs/plugins/ci-generic.zh.md +++ b/docs/plugins/ci-generic.zh.md @@ -4,6 +4,10 @@ ## 使用 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ``` yaml --8<-- "ci-generic.yaml" ``` diff --git a/docs/plugins/devlake-config.md b/docs/plugins/devlake-config.md new file mode 100644 index 000000000..8365b2a98 --- /dev/null +++ b/docs/plugins/devlake-config.md @@ -0,0 +1,9 @@ +# devlake-config plugin + +TODO(dtm): Add your document here. + +## Usage + +``` yaml +--8<-- "devlake-config.yaml" +``` diff --git a/docs/plugins/devlake-config.zh.md b/docs/plugins/devlake-config.zh.md new file mode 100644 index 000000000..c6496fb55 --- /dev/null +++ b/docs/plugins/devlake-config.zh.md @@ -0,0 +1,9 @@ +# devlake-config 插件 + +TODO(dtm): 在这里添加文档. + +## 用例 + +``` yaml +--8<-- "devlake-config.yaml" +``` diff --git a/docs/plugins/devlake.md b/docs/plugins/devlake.md index 7d4188e12..904ac8942 100644 --- a/docs/plugins/devlake.md +++ b/docs/plugins/devlake.md @@ -1,9 +1,13 @@ -# devlake plugin +# devlake Plugin //TODO(daniel-hutao): add document here after Chinese version be ready. ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ``` yaml --8<-- "devlake.yaml" ``` diff --git a/docs/plugins/devlake.zh.md b/docs/plugins/devlake.zh.md index 823e3dbce..dcb8c4cbe 100644 --- a/docs/plugins/devlake.zh.md +++ b/docs/plugins/devlake.zh.md @@ -4,6 +4,10 @@ ## 用例 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ``` yaml --8<-- "devlake.yaml" ``` diff --git a/docs/plugins/githubactions-golang.md b/docs/plugins/githubactions-golang.md index 52643c385..e9c25236d 100644 --- a/docs/plugins/githubactions-golang.md +++ b/docs/plugins/githubactions-golang.md @@ -19,6 +19,10 @@ _If Docker image build/push is enabled (see the example below), you also need to - DOCKERHUB_USERNAME - DOCKERHUB_TOKEN +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "githubactions-golang.yaml" ``` diff --git a/docs/plugins/githubactions-nodejs.md b/docs/plugins/githubactions-nodejs.md index 079a0dece..a186eaa3b 100644 --- a/docs/plugins/githubactions-nodejs.md +++ b/docs/plugins/githubactions-nodejs.md @@ -9,6 +9,10 @@ _This plugin depends on an environment variable "GITHUB_TOKEN". Set it before us If you don't know how to create this token, check out: - [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "githubactions-nodejs.yaml" ``` diff --git a/docs/plugins/githubactions-python.md b/docs/plugins/githubactions-python.md index f434d7f21..821bbb213 100644 --- a/docs/plugins/githubactions-python.md +++ b/docs/plugins/githubactions-python.md @@ -9,6 +9,10 @@ _This plugin depends on an environment variable "GITHUB_TOKEN". Set it before us If you don't know how to create this token, check out: - [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "githubactions-python.yaml" ``` diff --git a/docs/plugins/gitlab-ce-docker.md b/docs/plugins/gitlab-ce-docker.md index 18e7bb825..c0d4397ed 100644 --- a/docs/plugins/gitlab-ce-docker.md +++ b/docs/plugins/gitlab-ce-docker.md @@ -50,6 +50,10 @@ Note: 1. the user you are using must be `root` or in the `docker` group; 2. `https` isn't supported for now. +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "gitlab-ce-docker.yaml" ``` diff --git a/docs/plugins/gitlab-ce-docker.zh.md b/docs/plugins/gitlab-ce-docker.zh.md index 2c2c5f97f..04ea7b9ae 100644 --- a/docs/plugins/gitlab-ce-docker.zh.md +++ b/docs/plugins/gitlab-ce-docker.zh.md @@ -48,6 +48,10 @@ export GITLAB_HOME=/srv/gitlab 1. 你使用的用户必须是 `root` 或者在 `docker` 用户组里; 2. 目前暂不支持 `https` 方式访问 GitLab。 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ```yaml --8<-- "gitlab-ce-docker.yaml" ``` diff --git a/docs/plugins/gitlabci-generic.md b/docs/plugins/gitlabci-generic.md index 0aeea2897..4e6eb3622 100644 --- a/docs/plugins/gitlabci-generic.md +++ b/docs/plugins/gitlabci-generic.md @@ -14,6 +14,10 @@ If you are using self-hosted GitLab, refer to the [official doc here](https://do _Note: when creating the token, make sure you select "API" in the "scopes" section, as DevStream uses GitLab API to add CI workflow files._ +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + Plugin config example: ```yaml diff --git a/docs/plugins/gitlabci-golang.md b/docs/plugins/gitlabci-golang.md index 24f15644f..aa15a8f41 100644 --- a/docs/plugins/gitlabci-golang.md +++ b/docs/plugins/gitlabci-golang.md @@ -12,6 +12,10 @@ If you are using self-hosted GitLab, refer to the [official doc here](https://do _Note: when creating the token, make sure you select "API" in the "scopes" section, as DevStream uses GitLab API to add CI workflow files._ +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + Plugin config example: ```yaml diff --git a/docs/plugins/gitlabci-java.md b/docs/plugins/gitlabci-java.md index e23e87b2e..d72363abe 100644 --- a/docs/plugins/gitlabci-java.md +++ b/docs/plugins/gitlabci-java.md @@ -1,4 +1,4 @@ -# gitlabci-java plugin +# gitlabci-java Plugin This plugin set up Gitlab Pipeline in an existing Gitlab Java repository. @@ -12,6 +12,10 @@ This plugin set up Gitlab Pipeline in an existing Gitlab Java repository. 3. If `Deploy` is enabled, you need to offer the Gitlab Kubernetes agent name(see [Gitlab-Kubernetes](https://docs.gitlab.cn/jh/user/clusters/agent/) for more details). This will deploy the new built application to your Kubernetes cluster. This step will use `deployment.yaml` to automatically deploy the application. Please create `manifests` directory in the repository root and create your `deployment.yaml` configuration file in it. +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "gitlabci-java.yaml" ``` diff --git a/docs/plugins/gitlabci-java.zh.md b/docs/plugins/gitlabci-java.zh.md index d38542825..e44460625 100644 --- a/docs/plugins/gitlabci-java.zh.md +++ b/docs/plugins/gitlabci-java.zh.md @@ -10,6 +10,10 @@ 3. 如果`Deploy`选项被开启,你需要提供Gitlab配置的Kubernetes代理名称(设置详情参照[Gitlab-Kubernetes](https://docs.gitlab.cn/jh/user/clusters/agent/))。这会将新构建的应用部署至上面提供的Kubernetes集群中。该步骤会使用`deployment.yaml`来自动部署应用,请在仓库根目录下创建`manifests`目录,并在其中新建你的`deployment.yaml`配置文件。 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ```yaml --8<-- "gitlabci-java.yaml" ``` diff --git a/docs/plugins/harbor-docker.md b/docs/plugins/harbor-docker.md index a71046006..73ba0ef91 100644 --- a/docs/plugins/harbor-docker.md +++ b/docs/plugins/harbor-docker.md @@ -1,9 +1,13 @@ -# harbor-docker plugin +# harbor-docker Plugin // TODO(daniel-hutao): I'll add the docs here after Chinese docs be stable. ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ``` yaml --8<-- "harbor-docker.yaml" ``` diff --git a/docs/plugins/harbor-docker.zh.md b/docs/plugins/harbor-docker.zh.md index 7902771c7..bb2923f11 100644 --- a/docs/plugins/harbor-docker.zh.md +++ b/docs/plugins/harbor-docker.zh.md @@ -7,6 +7,10 @@ TODO(daniel-hutao): ## 用例 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ``` yaml --8<-- "harbor-docker.yaml" ``` diff --git a/docs/plugins/harbor.md b/docs/plugins/harbor.md index d9c266db7..a72451a44 100644 --- a/docs/plugins/harbor.md +++ b/docs/plugins/harbor.md @@ -33,6 +33,10 @@ Examples: ### 3.1 Quickstart +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + For a local testing and developing purpose, we can deploy Harbor quickly using the minimal config as follows: ```yaml diff --git a/docs/plugins/harbor.zh.md b/docs/plugins/harbor.zh.md index b681fc28b..19c5a71a8 100644 --- a/docs/plugins/harbor.zh.md +++ b/docs/plugins/harbor.zh.md @@ -39,6 +39,10 @@ minikube 方式部署的 k8s 集群自带一个默认的 StorageClass,另外 ### 3.1、快速开始 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + 如果仅是用于开发、测试等目的,希望快速完成 Harbor 的部署,可以使用如下配置快速开始: ```yaml title="config.yaml" diff --git a/docs/plugins/hashicorp-vault.md b/docs/plugins/hashicorp-vault.md index cd2b40003..cd3db86f4 100644 --- a/docs/plugins/hashicorp-vault.md +++ b/docs/plugins/hashicorp-vault.md @@ -6,6 +6,10 @@ This plugin installs hashicorp-vault with replicas:3 by default value. ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "hashicorp-vault.yaml" ``` diff --git a/docs/plugins/helm-generic.md b/docs/plugins/helm-generic.md index 67e5228d2..7887709ee 100644 --- a/docs/plugins/helm-generic.md +++ b/docs/plugins/helm-generic.md @@ -1,9 +1,13 @@ -# helm-generic plugin +# helm-generic Plugin // TODO ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ``` yaml --8<-- "helm-generic.yaml" ``` diff --git a/docs/plugins/helm-generic.zh.md b/docs/plugins/helm-generic.zh.md index d24a96050..da148c26f 100644 --- a/docs/plugins/helm-generic.zh.md +++ b/docs/plugins/helm-generic.zh.md @@ -4,6 +4,10 @@ ## 用例 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ``` yaml --8<-- "helm-generic.yaml" ``` diff --git a/docs/plugins/jenkins-pipeline.md b/docs/plugins/jenkins-pipeline.md index d12c66513..705581859 100644 --- a/docs/plugins/jenkins-pipeline.md +++ b/docs/plugins/jenkins-pipeline.md @@ -1,9 +1,13 @@ -# jenkins-pipeline plugin +# jenkins-pipeline Plugin TODO(dtm): Add your document here. ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ``` yaml --8<-- "jenkins-pipeline.yaml" ``` diff --git a/docs/plugins/jenkins-pipeline.zh.md b/docs/plugins/jenkins-pipeline.zh.md index 7d0ad6f53..eba266671 100644 --- a/docs/plugins/jenkins-pipeline.zh.md +++ b/docs/plugins/jenkins-pipeline.zh.md @@ -4,7 +4,7 @@ 本文将演示: -1. 通过 [`repo-scaffolding`](../repo-scaffolding.zh) 插件在 GitLab 上创建一个 Java Sprint Boot 项目脚手架; +1. 通过 [`repo-scaffolding`](./repo-scaffolding.zh.md) 插件在 GitLab 上创建一个 Java Sprint Boot 项目脚手架; 2. 通过 `jenkins-pipeline` 插件在 Jenkins 上创建一条 Java Spring Boot 的 CI 流水线; 3. 通过 `jenkins-pipeline` 插件实现在 GitLab 和 Jenkins 上分别配置相应参数,实现当 GitLab 上的代码库有 push 或者 merge 事件时,自动触发 Jenkins 上的流水线运行,同时流水线的执行结果自动回写到 GitLab 上。 @@ -54,6 +54,10 @@ export GITLAB_SSHKEY=YOUR_REPO_PRIVATE_KEY 然后准备 DevStream 插件配置: +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ```yaml tools: - name: repo-scaffolding @@ -99,7 +103,7 @@ tools: 除了这几个必须修改的配置项外,其他配置项你可以在确保理解含义的前提下灵活决定是否调整。 -*注意:这个配置示例仅是 tool config,完整的 DevStream 配置文件还需要补充 core config 等内容,具体参考[这个文档](../../core-concepts/config.zh)。* +*注意:这个配置示例仅是 tool config,完整的 DevStream 配置文件还需要补充 core config 等内容,具体参考[这个文档](../core-concepts/config.zh.md)。* ## 3、开始执行 diff --git a/docs/plugins/jenkins.md b/docs/plugins/jenkins.md index 8de41f46b..937998db8 100644 --- a/docs/plugins/jenkins.md +++ b/docs/plugins/jenkins.md @@ -6,7 +6,9 @@ It also installs [GitHub Pull Request Builder(ghprb)](https://plugins.jenkins.io ## Config -Please be sure to change the `storageClass` in the options of the config to an existing StorageClass. +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). ```yaml --8<-- "jenkins.yaml" @@ -26,6 +28,8 @@ Please be sure to change the `storageClass` in the options of the config to an e | repo.url | https://charts.jenkins.io | helm official repo address | | repo.name | jenkins | helm repo name | +Please be sure to change the `storageClass` in the options of the config to an existing StorageClass. + Currently, expect default configs all the parameters in the example above are mandatory. ## Outputs diff --git a/docs/plugins/jenkins.zh.md b/docs/plugins/jenkins.zh.md index 791bb0c92..1dbb7d4fb 100644 --- a/docs/plugins/jenkins.zh.md +++ b/docs/plugins/jenkins.zh.md @@ -26,6 +26,10 @@ Jenkins 的部署方式有很多种,比如 Docker、Kubernetes、Jar 包等等 ## 2.1、快速开始 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + 如果仅是用于开发、测试等目的,希望快速完成 Jenkins 的部署,可以使用如下配置快速开始: ```yaml diff --git a/docs/plugins/jira-github-integ.md b/docs/plugins/jira-github-integ.md index a784d16aa..e48520a96 100644 --- a/docs/plugins/jira-github-integ.md +++ b/docs/plugins/jira-github-integ.md @@ -20,6 +20,10 @@ If you don't know how to create these tokens, check out: - [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) - [Manage API tokens for your Atlassian account](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/). +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "jira-github-integ.yaml" ``` diff --git a/docs/plugins/kube-prometheus.md b/docs/plugins/kube-prometheus.md index 87660469a..98d578f52 100644 --- a/docs/plugins/kube-prometheus.md +++ b/docs/plugins/kube-prometheus.md @@ -4,6 +4,10 @@ This plugin installs [kube-prometheus](https://github.com/prometheus-operator/ku ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "kube-prometheus.yaml" ``` diff --git a/docs/plugins/openldap.md b/docs/plugins/openldap.md index 976405c1f..0638f70fb 100644 --- a/docs/plugins/openldap.md +++ b/docs/plugins/openldap.md @@ -4,6 +4,10 @@ This plugin installs [OpenLDAP](https://www.openldap.org/) in an existing Kubern ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "openldap.yaml" ``` diff --git a/docs/plugins/repo-scaffolding.md b/docs/plugins/repo-scaffolding.md index 668311d7c..573525d71 100644 --- a/docs/plugins/repo-scaffolding.md +++ b/docs/plugins/repo-scaffolding.md @@ -1,4 +1,4 @@ -# repo-scaffolding plugin +# repo-scaffolding Plugin This plugin bootstraps a GitHub or GitLab repo with scaffolding code for a web application. @@ -28,7 +28,9 @@ This plugin need fllowing config base on your repo type: ## Usage -**Please note that all parameter is case-sensitive.** +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). ```yaml --8<-- "repo-scaffolding.yaml" @@ -46,6 +48,8 @@ This configuration is used for the target repo, it includes the following config - `YOUR_DESTINATION_REPO_MAIN_BRANCH` - `YOUR_DESTINATION_REPO_TYPE` +**Please note that all parameter is case-sensitive.** + Currently, `owner`, `org`, and `repo` are mandatory, `branch` has the default value "main", `repoType` support `gitlab` and `github` for now. ### sourceRepo diff --git a/docs/plugins/repo-scaffolding.zh.md b/docs/plugins/repo-scaffolding.zh.md index e37790741..dff0afd85 100644 --- a/docs/plugins/repo-scaffolding.zh.md +++ b/docs/plugins/repo-scaffolding.zh.md @@ -26,12 +26,16 @@ ## 使用方法 -**请注意这里的设置参数都是大小写敏感的** +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). ```yaml --8<-- "repo-scaffolding.yaml" ``` +**请注意这里的设置参数都是大小写敏感的** + 在配置文件中替换以下配置: ### destinationRepo diff --git a/docs/plugins/sonarqube.md b/docs/plugins/sonarqube.md index 7c8e6aab0..5e032e552 100644 --- a/docs/plugins/sonarqube.md +++ b/docs/plugins/sonarqube.md @@ -1,9 +1,13 @@ -# sonarqube plugin +# sonarqube Plugin TODO(jiafeng meng): I will add this document later. ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ``` yaml --8<-- "sonarqube.yaml" ``` diff --git a/docs/plugins/sonarqube.zh.md b/docs/plugins/sonarqube.zh.md index 9841cbdfd..8f162d50b 100644 --- a/docs/plugins/sonarqube.zh.md +++ b/docs/plugins/sonarqube.zh.md @@ -33,6 +33,10 @@ Sonarqube 内部会使用 Elastcisearch 来做搜索的索引,所以生产环 | repo.url | https://SonarSource.github.io/helm-chart-sonarqube| helm 仓库地址 | | repo.name | sonarqube | helm 仓库名 | +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + 因此完整的配置文件应该是这样: ```yaml diff --git a/docs/plugins/tekton.md b/docs/plugins/tekton.md index ef6296245..f7a60399f 100644 --- a/docs/plugins/tekton.md +++ b/docs/plugins/tekton.md @@ -3,6 +3,10 @@ This plugin installs [tekton](https://tekton.dev/) in an existing Kubernetes clu ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "tekton.yaml" ``` diff --git a/docs/plugins/tekton.zh.md b/docs/plugins/tekton.zh.md index 5a320fafc..c0591c641 100644 --- a/docs/plugins/tekton.zh.md +++ b/docs/plugins/tekton.zh.md @@ -2,6 +2,10 @@ ## 用例 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ```yaml --8<-- "tekton.yaml" ``` diff --git a/docs/plugins/trello-github-integ.md b/docs/plugins/trello-github-integ.md index 940635905..f4f34a1da 100644 --- a/docs/plugins/trello-github-integ.md +++ b/docs/plugins/trello-github-integ.md @@ -6,6 +6,10 @@ This plugin creates a new GitHub Actions workflow(trello-github-integration) and This plugin depends on and can be used together with the `trello` plugin (see document [here](./trello.md)). +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + `trello-github-integ` plugin can also use `trello` plugin's outputs as input. See the example below: ```yaml diff --git a/docs/plugins/trello.md b/docs/plugins/trello.md index be9c7f3cb..241edaaae 100644 --- a/docs/plugins/trello.md +++ b/docs/plugins/trello.md @@ -16,6 +16,10 @@ _Trello board description is managed by DevStream, please don't modify it._ To create a Trello API key and token, see [here](https://trello.com/app-key). +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --8<-- "trello.yaml" ``` diff --git a/docs/plugins/zentao.md b/docs/plugins/zentao.md index f6162805a..e97b42155 100644 --- a/docs/plugins/zentao.md +++ b/docs/plugins/zentao.md @@ -11,6 +11,10 @@ This plugin installs [ZenTao](https://zentao.net/) in an existing Kubernetes clu ## Usage +The following content is an example of the "tool file". + +For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/core-concepts.md#1-config) and [DevStream Configuration](../core-concepts/config.md). + ```yaml --- # core config diff --git a/docs/plugins/zentao.zh.md b/docs/plugins/zentao.zh.md index 77b9b0222..c4c4a14a5 100644 --- a/docs/plugins/zentao.zh.md +++ b/docs/plugins/zentao.zh.md @@ -10,6 +10,10 @@ ## 用法示例 +下面的配置文件展示的是"tool file"的内容。 + +关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/core-concepts.zh.md)和[DevStream配置](../core-concepts/config.zh.md). + ```yaml --- # core config diff --git a/docs/quickstart.md b/docs/quickstart.md index 1d8c92c13..e41a15815 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,15 +14,23 @@ In this quickstart, we will do the following automatically with DevStream: In your working directory, run: ```shell -sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/quick-start/quickstart.sh)" +sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/install/download.sh)" ``` -This will download the `dtm` binary and a `quickstart.yaml` config file to your working directory, and grant the binary execution permission. +This will download the corresponding `dtm` binary to your working directory according to your OS and chip architecture, and grant the binary execution permission. > Optional: you can then move `dtm` to a place which is in your PATH. For example: `mv dtm /usr/local/bin/`. +_For more details on how to install, see [install dtm](./install.md)._ + ## 2 Configuration +Run the following command to generate the template configuration file `config.yaml` for quickstart. + +```shell +./dtm show config -t quickstart > config.yaml +``` + As aforementioned, we will handle GitHub repo scaffolding and CI workflows in GitHub Actions, so, we will need the following environment variables (env vars) to be set: - GITHUB_USER diff --git a/docs/quickstart.zh.md b/docs/quickstart.zh.md index fbb68738c..7ad4ca749 100644 --- a/docs/quickstart.zh.md +++ b/docs/quickstart.zh.md @@ -1,6 +1,6 @@ # 快速开始 -如果你更喜欢看 DevStream 的实际操作,请先观看[演示视频](./index.md)。 +如果你更喜欢看 DevStream 的实际操作,请先观看[演示视频](./index.zh.md)。 > 注意:DevStream 目前只有 Linux 和 macOS 版本,Windows 将在以后支持。 @@ -12,16 +12,25 @@ ## 1 下载 进入你的工作目录,运行: + ```shell -sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/quick-start/quickstart.sh)" +sh -c "$(curl -fsSL https://raw.githubusercontent.com/devstream-io/devstream/main/hack/install/download.sh)" ``` -这个命令会下载 `dtm` 二进制文件和 `quickstart.yaml` 的配置文件到你的工作目录中,并赋予二进制文件执行权限。 +这个命令会根据你的操作系统和芯片架构下载对应的 `dtm` 二进制文件到你的工作目录中,并赋予二进制文件执行权限。 > 可选:建议你将 dtm 移动到包含于 PATH 的目录下,比如 `mv dtm /usr/local/bin/`。 +_更多安装方式详见[安装dtm](./install.zh.md)。_ + ## 2 配置 +运行以下命令来生成 quickstart 的模板配置文件 `config.yaml` 。 + +```shell +./dtm show config -t quickstart > config.yaml +``` + 正如前文所述,我们将在 GitHub Actions 中操作 GitHub 仓库的脚手架和 CI 工作流。所以,我们需要设置以下环境变量: - GITHUB_USER diff --git a/go.mod b/go.mod index a6bd78f09..e3b1e08ea 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/cheggaaa/pb v1.0.29 github.com/deckarep/golang-set/v2 v2.1.0 github.com/go-playground/validator/v10 v10.11.0 - github.com/go-resty/resty/v2 v2.7.0 github.com/google/go-cmp v0.5.8 github.com/google/go-github/v42 v42.0.0 github.com/imdario/mergo v0.3.12 @@ -26,17 +25,17 @@ require ( github.com/onsi/gomega v1.19.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.4.0 + github.com/spf13/cobra v1.5.0 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 - github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220812023423-ab97a51a0978 + github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 github.com/xanzy/go-gitlab v0.55.1 go.uber.org/multierr v1.6.0 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 gopkg.in/gookit/color.v1 v1.1.6 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + gopkg.in/yaml.v3 v3.0.1 gotest.tools v2.2.0+incompatible helm.sh/helm/v3 v3.7.2 k8s.io/api v0.22.4 @@ -53,8 +52,9 @@ require ( github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.2 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/Microsoft/go-winio v0.4.17 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect @@ -93,21 +93,22 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/fatih/color v1.9.0 // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/fvbommel/sortorder v1.0.1 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-errors/errors v1.0.1 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect - github.com/go-git/go-billy/v5 v5.0.0 // indirect - github.com/go-git/go-git/v5 v5.2.0 // indirect + github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-git/go-git/v5 v5.4.2 // indirect github.com/go-logr/logr v0.4.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.21.1 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-redis/cache/v8 v8.4.2 // indirect github.com/go-redis/redis/v8 v8.11.3 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.0.0 // indirect @@ -117,7 +118,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect @@ -133,17 +134,17 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/lib/pq v1.10.0 // indirect + github.com/lib/pq v1.10.2 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.5 // indirect - github.com/mailru/easyjson v0.7.6 // indirect - github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.1.1 // indirect @@ -166,7 +167,8 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/robfig/cron v1.1.0 // indirect + github.com/robfig/cron v1.2.0 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect github.com/russross/blackfriday v1.5.2 // indirect github.com/sergi/go-diff v1.1.0 // indirect @@ -174,28 +176,29 @@ require ( github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1 // indirect + github.com/stretchr/objx v0.3.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/vmihailenco/go-tinylfu v0.2.1 // indirect github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/xanzy/ssh-agent v0.2.1 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - go.uber.org/atomic v1.7.0 // indirect + go.uber.org/atomic v1.9.0 // indirect golang.org/x/exp v0.0.0-20210901193431-a062eea981d2 // indirect - golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect + golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect - google.golang.org/grpc v1.38.0 // indirect + google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect + google.golang.org/grpc v1.44.0 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/gorp.v1 v1.7.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 67641f997..f7eac6158 100644 --- a/go.sum +++ b/go.sum @@ -105,10 +105,10 @@ github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:m github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= @@ -116,11 +116,12 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d/go.mod h1:WML6KOYjeU8N6YyusMjj2qRvaPNUEvrQvaxuFcMRFJY= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/adlio/trello v1.9.0 h1:b8R1oic2yksok5McAd+kcfsvTq5sX+Fv8rMTSpBBRq8= github.com/adlio/trello v1.9.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -264,6 +265,10 @@ github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1w github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= @@ -377,7 +382,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= @@ -450,6 +455,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -474,8 +480,9 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= @@ -489,16 +496,21 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= -github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= +github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -534,8 +546,9 @@ github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3Hfo github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -557,8 +570,9 @@ github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= @@ -574,11 +588,10 @@ github.com/go-redis/cache/v8 v8.4.2 h1:8YbsmnU1Ws3TKS6T+qALzYE/MlGE+A/lrlx1XTA3p github.com/go-redis/cache/v8 v8.4.2/go.mod h1:X7Jjd69Ssbrf3xBQLtIDE0g3WcSbFoQiSGeb8QfEJ+g= github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8= github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= @@ -704,8 +717,9 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= @@ -801,6 +815,7 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -834,8 +849,9 @@ github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -869,8 +885,9 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= @@ -888,8 +905,9 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/malexdev/utfutil v0.0.0-20180510171754-00c8d4a8e7a8/go.mod h1:UtpLyb/EupVKXF/N0b4NRe1DNg+QYJsnsHQ038romhM= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= @@ -898,15 +916,19 @@ github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2 github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1135,16 +1157,18 @@ github.com/quobyte/api v0.1.8/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H github.com/r3labs/diff v1.1.0/go.mod h1:7WjXasNzi0vJetRcB/RqNl5dlIsmXcTTLmF5IoH6Xig= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= -github.com/robfig/cron v1.1.0 h1:jk4/Hud3TTdcrJgUOBgsqrZBarcxl6ADIjSC2iniwLY= github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= @@ -1200,8 +1224,8 @@ github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1209,8 +1233,9 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1 h1:zrNp7OPtn2fjeNHI9CghvwxqQvvkK0RxUo86hE86vhU= +github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= @@ -1224,8 +1249,9 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= +github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1268,12 +1294,13 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220812023423-ab97a51a0978 h1:kIJVIF7qSnjYauW8d0kV+GGjzzTmULt8vHen1WhYcik= -github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220812023423-ab97a51a0978/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k= +github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I= +github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k= github.com/xanzy/go-gitlab v0.55.1 h1:IgX/DS9buV0AUz8fuJPQkdl0fQGfBiAsAHxpun8sNhg= github.com/xanzy/go-gitlab v0.55.1/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM= -github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -1343,8 +1370,9 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -1385,6 +1413,7 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -1491,14 +1520,14 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I= +golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1618,13 +1647,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1809,8 +1841,9 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf h1:SVYXkUz2yZS9FWb2Gm8ivSlbNQzL2Z/NpPKE3RG2jWk= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1838,8 +1871,9 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1902,8 +1936,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/hack/install/download.sh b/hack/install/download.sh index 76a112f22..b5716a500 100644 --- a/hack/install/download.sh +++ b/hack/install/download.sh @@ -23,12 +23,17 @@ function init() { } function getLatestReleaseVersion() { - latestVersion=$(curl -s https://api.github.com/repos/devstream-io/devstream/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [ -n "${GITHUB_TOKEN}" ]; then + AUTH_HEADER="-H Authorization: token ${GITHUB_TOKEN}" + fi + + # like "v1.2.3" + latestVersion=$(curl ${AUTH_HEADER} -s https://api.github.com/repos/devstream-io/devstream/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') if [ -z "$latestVersion" ]; then echo "Failed to get latest release version" exit 1 fi - echo "Latest dtm release version: ${latestVersion}\n" + echo "Latest dtm release version: ${latestVersion}" } function downloadDtm() { diff --git a/hack/quick-start/quickstart.sh b/hack/quick-start/quickstart.sh deleted file mode 100755 index 0ff9e0372..000000000 --- a/hack/quick-start/quickstart.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/bash - -function init() { - if [ "$(uname)" == "Darwin" ];then - HOST_OS="darwin" - elif [ "$(uname)" == "Linux" ];then - HOST_OS="linux" - else - echo "Support Darwin/Linux OS only" - exit 1 - fi - - if [ "$(uname -m)" == "amd64" ] || [ "$(uname -m)" == "x86_64" ];then - HOST_ARCH="amd64" - elif [ "$(uname -m)" == "arm64" ];then - HOST_ARCH="arm64" - else - echo "Support amd64/arm64 CPU arch only" - exit 1 - fi - - echo "Got OS type: ${HOST_OS} and CPU arch: ${HOST_ARCH}" -} - -function getLatestReleaseVersion() { - if [ -n "${GITHUB_TOKEN}" ]; then - AUTH_HEADER="-H Authorization: token ${GITHUB_TOKEN}" - fi - - # like "v1.2.3" - latestVersion=$(curl ${AUTH_HEADER} -s https://api.github.com/repos/devstream-io/devstream/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - if [ -z "$latestVersion" ]; then - echo "Failed to get latest release version" - exit 1 - fi - echo "Latest dtm release version: ${latestVersion}" -} - -function downloadDtm() { - # 1. download the release and rename it to "dtm" - # 2. count the download count of the release - fullReleaseUrl="https://devstream.gateway.scarf.sh/releases/$latestVersion/dtm-$HOST_OS-$HOST_ARCH" - echo "Downloading dtm from: $fullReleaseUrl" - # use -L to follow redirects - curl -L -o dtm $fullReleaseUrl - echo "dtm downloaded completed\n" - - # grant execution rights - chmod +x dtm -} - -function downloadQuickStartConfig() { - # convert the latest version to a valid branch name - branchName=`semverToBranch $latestVersion` - if [ -z "$branchName" ]; then - echo "Failed to get branch name from version: $latestVersion" - exit 1 - fi - # config file is small so we use -s to ignore the output - quickstartConfigUrl=https://raw.githubusercontent.com/devstream-io/devstream/${branchName}/examples/quickstart.yaml - echo "Downloading quickstart config file from ${quickstartConfigUrl}" - curl -s -o quickstart.yaml ${quickstartConfigUrl} - echo "quickstart.yaml downloaded completed" -} - -# convert semver to branch name -# e.g. v1.2.3 -> release-1.2 -# ref: https://docs.devstream.io/en/latest/development/branch-and-release/#3-correspondence-between-branch-name-and-version-number -function semverToBranch() { - semver=$1 - # remove the leading "v" - semver=${semver:1} - # remove the last ".x" - semver=${semver%.*} - echo -n "release-${semver}" -} - -function showDtmHelp() { - echo "" - # show dtm help and double check the download is success - ./dtm help -} - -init -getLatestReleaseVersion -downloadDtm -downloadQuickStartConfig -showDtmHelp - diff --git a/internal/pkg/configmanager/configmanager.go b/internal/pkg/configmanager/configmanager.go index 13bb29c4f..f45f65b7f 100644 --- a/internal/pkg/configmanager/configmanager.go +++ b/internal/pkg/configmanager/configmanager.go @@ -177,6 +177,7 @@ func (m *Manager) loadOriginalConfigFile() ([]byte, error) { // toolFile: "" # If not empty, use the specified external tools config file // pluginDir: "" # If empty, use the default value: ~/.devstream/plugins, or use -d flag to specify a directory // state: +// // backend: local // options: // stateFile: devstream.state @@ -188,9 +189,9 @@ func (m *Manager) loadOriginalConfigFile() ([]byte, error) { // --- // # plugins config // tools: -// - name: A-PLUGIN-NAME -// instanceID: default -// options: +// - name: A-PLUGIN-NAME +// instanceID: default +// options: // foo: bar // // See https://github.com/devstream-io/devstream/issues/596 for more details. diff --git a/internal/pkg/plugin/devlake/create.go b/internal/pkg/plugin/devlake/create.go index 096ca253f..5f6609483 100644 --- a/internal/pkg/plugin/devlake/create.go +++ b/internal/pkg/plugin/devlake/create.go @@ -15,7 +15,7 @@ func Create(options map[string]interface{}) (map[string]interface{}, error) { }, ExecuteOperations: helm.DefaultCreateOperations, TerminateOperations: helm.DefaultTerminateOperations, - GetStateOperation: helm.GetPluginAllState, + GetStateOperation: genDevLakeState, } // Execute all Operations in Operator diff --git a/internal/pkg/plugin/devlake/devlake.go b/internal/pkg/plugin/devlake/devlake.go index 086253516..5c841f572 100644 --- a/internal/pkg/plugin/devlake/devlake.go +++ b/internal/pkg/plugin/devlake/devlake.go @@ -1,11 +1,18 @@ package devlake import ( + "fmt" + + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" "github.com/devstream-io/devstream/internal/pkg/plugininstaller/helm" + "github.com/devstream-io/devstream/internal/pkg/statemanager" helmCommon "github.com/devstream-io/devstream/pkg/util/helm" + "github.com/devstream-io/devstream/pkg/util/k8s" "github.com/devstream-io/devstream/pkg/util/types" ) +const DevLakeSvcName = "devlake-lake" + // TODO(daniel-hutao): update the config below after devlake chart released. var defaultHelmConfig = helm.Options{ Chart: helmCommon.Chart{ @@ -22,3 +29,48 @@ var defaultHelmConfig = helm.Options{ Name: "devlake", }, } + +func genDevLakeState(options plugininstaller.RawOptions) (statemanager.ResourceState, error) { + resState, err := helm.GetPluginAllState(options) + if err != nil { + return nil, err + } + + // values.yaml + opt, err := helm.NewOptions(options) + if err != nil { + return nil, err + } + valuesYaml := opt.GetHelmParam().Chart.ValuesYaml + resState["valuesYaml"] = valuesYaml + + // TODO(daniel-hutao): Use Ingress later. + ip, err := getDevLakeClusterIP(opt.Chart.Namespace, DevLakeSvcName) + if err != nil { + return nil, err + } + url := fmt.Sprintf("http://%s:8080", ip) + outputs := map[string]interface{}{ + "devlake_url": url, + } + resState.SetOutputs(outputs) + + return resState, nil +} + +func getDevLakeClusterIP(namespace, name string) (string, error) { + kClient, err := k8s.NewClient() + if err != nil { + return "", err + } + + svc, err := kClient.GetService(namespace, name) + if err != nil { + return "", err + } + + if svc.Spec.ClusterIP == "" { + return "", fmt.Errorf("cluster ip is empty") + } + return svc.Spec.ClusterIP, nil +} diff --git a/internal/pkg/plugin/devlake/read.go b/internal/pkg/plugin/devlake/read.go index abe6a7862..a2d294e08 100644 --- a/internal/pkg/plugin/devlake/read.go +++ b/internal/pkg/plugin/devlake/read.go @@ -13,7 +13,7 @@ func Read(options map[string]interface{}) (map[string]interface{}, error) { helm.SetDefaultConfig(&defaultHelmConfig), helm.Validate, }, - GetStateOperation: helm.GetPluginAllState, + GetStateOperation: genDevLakeState, } // Execute all Operations in Operator diff --git a/internal/pkg/plugin/devlake/update.go b/internal/pkg/plugin/devlake/update.go index 84b942832..e97fb02b2 100644 --- a/internal/pkg/plugin/devlake/update.go +++ b/internal/pkg/plugin/devlake/update.go @@ -14,7 +14,7 @@ func Update(options map[string]interface{}) (map[string]interface{}, error) { helm.Validate, }, ExecuteOperations: helm.DefaultUpdateOperations, - GetStateOperation: helm.GetPluginAllState, + GetStateOperation: genDevLakeState, } // Execute all Operations in Operator diff --git a/internal/pkg/plugin/devlakeconfig/create.go b/internal/pkg/plugin/devlakeconfig/create.go new file mode 100644 index 000000000..ee0741652 --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/create.go @@ -0,0 +1,31 @@ +package devlakeconfig + +import ( + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" + "github.com/devstream-io/devstream/pkg/util/log" +) + +func Create(options map[string]interface{}) (map[string]interface{}, error) { + // Initialize Operator with Operations + operator := &plugininstaller.Operator{ + PreExecuteOperations: plugininstaller.PreExecuteOperations{ + validate, + RenderAuthConfig, + }, + ExecuteOperations: plugininstaller.ExecuteOperations{ + ApplyConfig, + }, + TerminateOperations: plugininstaller.TerminateOperations{ + // TODO(dtm): Add your TerminateOperations here. + }, + GetStateOperation: GetState, + } + + // Execute all Operations in Operator + status, err := operator.Execute(plugininstaller.RawOptions(options)) + if err != nil { + return nil, err + } + log.Debugf("Return map: %v", status) + return status, nil +} diff --git a/internal/pkg/plugin/devlakeconfig/delete.go b/internal/pkg/plugin/devlakeconfig/delete.go new file mode 100644 index 000000000..0839d0060 --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/delete.go @@ -0,0 +1,26 @@ +package devlakeconfig + +import ( + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" +) + +func Delete(options map[string]interface{}) (bool, error) { + // Initialize Operator with Operations + operator := &plugininstaller.Operator{ + PreExecuteOperations: plugininstaller.PreExecuteOperations{ + validate, + RenderAuthConfig, + }, + ExecuteOperations: plugininstaller.ExecuteOperations{ + DeleteConfig, + }, + } + + // Execute all Operations in Operator + _, err := operator.Execute(plugininstaller.RawOptions(options)) + if err != nil { + return false, err + } + + return true, nil +} diff --git a/internal/pkg/plugin/devlakeconfig/devlakeconfig.go b/internal/pkg/plugin/devlakeconfig/devlakeconfig.go new file mode 100644 index 000000000..a1f53f169 --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/devlakeconfig.go @@ -0,0 +1,109 @@ +package devlakeconfig + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" + "github.com/devstream-io/devstream/internal/pkg/statemanager" + "github.com/devstream-io/devstream/pkg/util/log" +) + +var httpClient = &http.Client{ + Timeout: 5 * time.Second, +} + +func RenderAuthConfig(options plugininstaller.RawOptions) (plugininstaller.RawOptions, error) { + opts, err := NewOptions(options) + if err != nil { + return nil, err + } + + for _, p := range opts.Plugins { + for _, c := range p.Connections { + c.Token = c.Authx.Token + c.Username = c.Authx.Username + c.Password = c.Authx.Password + c.AppId = c.Authx.AppId + c.SecretKey = c.Authx.SecretKey + } + } + + return opts.Encode() +} + +func ApplyConfig(options plugininstaller.RawOptions) error { + opts, err := NewOptions(options) + if err != nil { + return err + } + + for _, p := range opts.Plugins { + log.Infof("Got DevLake plugin config: %s. Connections: ", p.Name) + if err := createConnections(opts.DevLakeAddr, p.Name, p.Connections); err != nil { + return err + } + } + + return nil +} + +func createConnections(host string, pluginName string, connections []Connection) error { + for i, c := range connections { + log.Infof("Connection %d: %s", i, c.Name) + configs, err := json.Marshal(c) + if err != nil { + return err + } + log.Debugf("Connection configs: %s", string(configs)) + + url := fmt.Sprintf("%s/plugins/%s/connections", strings.TrimRight(host, "/"), pluginName) + log.Debugf("URL: %s", url) + + if err := createConnection(url, configs); err != nil { + return err + } + } + + log.Infof("All %s connections have been created.", pluginName) + return nil +} + +func createConnection(url string, bodyWithJson []byte) error { + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(bodyWithJson)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + resp, err := httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + return nil + } + + return fmt.Errorf(resp.Status) +} + +func DeleteConfig(options plugininstaller.RawOptions) error { + // TODO(daniel-hutao): implement later + return nil +} + +func UpdateConfig(options plugininstaller.RawOptions) error { + // TODO(daniel-hutao): implement later + return nil +} + +func GetState(options plugininstaller.RawOptions) (statemanager.ResourceState, error) { + resState := statemanager.ResourceState(options) + return resState, nil +} diff --git a/internal/pkg/plugin/devlakeconfig/options.go b/internal/pkg/plugin/devlakeconfig/options.go new file mode 100644 index 000000000..17994326c --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/options.go @@ -0,0 +1,62 @@ +package devlakeconfig + +import ( + "github.com/mitchellh/mapstructure" + + "github.com/devstream-io/devstream/internal/pkg/plugin/devlakeconfig/staging" + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" +) + +// Options is the struct for configurations of the devlake-config plugin. +type Options struct { + DevLakeAddr string `mapstructure:"devlakeAddr" validate:"url"` + Plugins []DevLakePlugin `mapstructure:"plugins" validate:"required"` +} + +// NewOptions create options by raw options +func NewOptions(options plugininstaller.RawOptions) (Options, error) { + var opts Options + if err := mapstructure.Decode(options, &opts); err != nil { + return opts, err + } + return opts, nil +} + +type DevLakePlugin struct { + Name string `mapstructure:"name" validate:"required"` + Connections []Connection `mapstructure:"connections"` +} + +// TODO(daniel-hutao): uncomment the code below after DevLake fix the umimportable issue: +// +//github.com/devstream-io/devstream/internal/pkg/plugin/devlakeconfig imports +// github.com/apache/incubator-devlake/plugins/helper tested by +// github.com/apache/incubator-devlake/plugins/helper.test imports +// github.com/apache/incubator-devlake/mocks: module github.com/apache/incubator-devlake@latest found (v0.14.0), but does not contain package github.com/apache/incubator-devlake/mocks +// +//type Connection struct { +// helper.RestConnection `mapstructure:",squash"` +// helper.BasicAuth `mapstructure:",squash"` +// helper.AccessToken `mapstructure:",squash"` +// helper.AppKey `mapstructure:",squash"` +//} + +type Connection struct { + staging.RestConnection `mapstructure:",squash"` + Authx Auth `mapstructure:"auth" validate:"required"` + Auth `mapstructure:",squash"` +} + +type Auth struct { + staging.BasicAuth `mapstructure:",squash"` + staging.AccessToken `mapstructure:",squash"` + staging.AppKey `mapstructure:",squash"` +} + +func (o *Options) Encode() (map[string]interface{}, error) { + var options map[string]interface{} + if err := mapstructure.Decode(o, &options); err != nil { + return nil, err + } + return options, nil +} diff --git a/internal/pkg/plugin/devlakeconfig/read.go b/internal/pkg/plugin/devlakeconfig/read.go new file mode 100644 index 000000000..62eddf345 --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/read.go @@ -0,0 +1,25 @@ +package devlakeconfig + +import ( + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" + "github.com/devstream-io/devstream/pkg/util/log" +) + +func Read(options map[string]interface{}) (map[string]interface{}, error) { + // Initialize Operator with Operations + operator := &plugininstaller.Operator{ + PreExecuteOperations: plugininstaller.PreExecuteOperations{ + validate, + RenderAuthConfig, + }, + GetStateOperation: GetState, + } + + // Execute all Operations in Operator + status, err := operator.Execute(plugininstaller.RawOptions(options)) + if err != nil { + return nil, err + } + log.Debugf("Return map: %v", status) + return status, nil +} diff --git a/internal/pkg/plugin/devlakeconfig/staging/common/base.go b/internal/pkg/plugin/devlakeconfig/staging/common/base.go new file mode 100644 index 000000000..5efa2133d --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/staging/common/base.go @@ -0,0 +1,54 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 common + +import ( + "regexp" + "time" +) + +type Model struct { + ID uint64 `gorm:"primaryKey" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type NoPKModel struct { + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + RawDataOrigin +} + +// embedded fields for tool layer tables +type RawDataOrigin struct { + // can be used for flushing outdated records from table + RawDataParams string `gorm:"column:_raw_data_params;type:varchar(255);index" json:"_raw_data_params"` + RawDataTable string `gorm:"column:_raw_data_table;type:varchar(255)" json:"_raw_data_table"` + // can be used for debugging + RawDataId uint64 `gorm:"column:_raw_data_id" json:"_raw_data_id"` + // we can store record index into this field, which is helpful for debugging + RawDataRemark string `gorm:"column:_raw_data_remark" json:"_raw_data_remark"` +} + +var ( + DUPLICATE_REGEX = regexp.MustCompile(`(?i)\bduplicate\b`) +) + +func IsDuplicateError(err error) bool { + return err != nil && DUPLICATE_REGEX.MatchString(err.Error()) +} diff --git a/internal/pkg/plugin/devlakeconfig/staging/connection.go b/internal/pkg/plugin/devlakeconfig/staging/connection.go new file mode 100644 index 000000000..ccb5b155c --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/staging/connection.go @@ -0,0 +1,36 @@ +package staging + +import ( + "github.com/devstream-io/devstream/internal/pkg/plugin/devlakeconfig/staging/common" +) + +// BaseConnection FIXME ... +type BaseConnection struct { + Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" validate:"required"` + common.Model +} + +// BasicAuth FIXME ... +type BasicAuth struct { + Username string `mapstructure:"username" validate:"required" json:"username"` + Password string `mapstructure:"password" validate:"required" json:"password" encrypt:"yes"` +} + +// AccessToken FIXME ... +type AccessToken struct { + Token string `mapstructure:"token" validate:"required" json:"token" encrypt:"yes"` +} + +// AppKey FIXME ... +type AppKey struct { + AppId string `mapstructure:"app_id" validate:"required" json:"appId"` + SecretKey string `mapstructure:"secret_key" validate:"required" json:"secretKey" encrypt:"yes"` +} + +// RestConnection FIXME ... +type RestConnection struct { + BaseConnection `mapstructure:",squash"` + Endpoint string `mapstructure:"endpoint" validate:"required" json:"endpoint"` + Proxy string `mapstructure:"proxy" json:"proxy"` + RateLimitPerHour int `comment:"api request rate limit per hour" json:"rateLimitPerHour"` +} diff --git a/internal/pkg/plugin/devlakeconfig/update.go b/internal/pkg/plugin/devlakeconfig/update.go new file mode 100644 index 000000000..eaab9e284 --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/update.go @@ -0,0 +1,31 @@ +package devlakeconfig + +import ( + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" + "github.com/devstream-io/devstream/pkg/util/log" +) + +func Update(options map[string]interface{}) (map[string]interface{}, error) { + // Initialize Operator with Operations + operator := &plugininstaller.Operator{ + PreExecuteOperations: plugininstaller.PreExecuteOperations{ + validate, + RenderAuthConfig, + }, + ExecuteOperations: plugininstaller.ExecuteOperations{ + UpdateConfig, + }, + TerminateOperations: plugininstaller.TerminateOperations{ + // TODO(dtm): Add your TerminateOperations here. + }, + GetStateOperation: GetState, + } + + // Execute all Operations in Operator + status, err := operator.Execute(plugininstaller.RawOptions(options)) + if err != nil { + return nil, err + } + log.Debugf("Return map: %v", status) + return status, nil +} diff --git a/internal/pkg/plugin/devlakeconfig/validate.go b/internal/pkg/plugin/devlakeconfig/validate.go new file mode 100644 index 000000000..3c23d084f --- /dev/null +++ b/internal/pkg/plugin/devlakeconfig/validate.go @@ -0,0 +1,25 @@ +package devlakeconfig + +import ( + "fmt" + + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" + "github.com/devstream-io/devstream/pkg/util/log" + "github.com/devstream-io/devstream/pkg/util/validator" +) + +// validate validates the options provided by the core. +func validate(options plugininstaller.RawOptions) (plugininstaller.RawOptions, error) { + opts, err := NewOptions(options) + if err != nil { + return nil, err + } + errs := validator.Struct(opts) + if len(errs) > 0 { + for _, e := range errs { + log.Errorf("Options error: %s.", e) + } + return nil, fmt.Errorf("opts are illegal") + } + return options, nil +} diff --git a/internal/pkg/plugin/gitlabcedocker/options_test.go b/internal/pkg/plugin/gitlabcedocker/options_test.go index de78cb906..9bcd20906 100644 --- a/internal/pkg/plugin/gitlabcedocker/options_test.go +++ b/internal/pkg/plugin/gitlabcedocker/options_test.go @@ -47,6 +47,5 @@ var _ = Describe("Options", func() { Expect(OptsBuild).To(Equal(OptsExpect)) }) - }) }) diff --git a/internal/pkg/plugin/gitlabci/java/create.go b/internal/pkg/plugin/gitlabci/java/create.go index ca44923dc..19e1ce7ce 100644 --- a/internal/pkg/plugin/gitlabci/java/create.go +++ b/internal/pkg/plugin/gitlabci/java/create.go @@ -37,7 +37,7 @@ func Create(options map[string]interface{}) (map[string]interface{}, error) { if err != nil { return nil, err } - _, err = client.PushLocalFileToRepo(&git.CommitInfo{ + _, err = client.PushFiles(&git.CommitInfo{ CommitMsg: commitMessage, CommitBranch: opts.Branch, GitFileMap: git.GitFileContentMap{ diff --git a/internal/pkg/plugin/gitlabci/java/read.go b/internal/pkg/plugin/gitlabci/java/read.go index 051ae3c50..ad7eb86c5 100644 --- a/internal/pkg/plugin/gitlabci/java/read.go +++ b/internal/pkg/plugin/gitlabci/java/read.go @@ -26,12 +26,12 @@ func Read(options map[string]interface{}) (map[string]interface{}, error) { return nil, err } - exists, err := client.FileExists(ciFileName) + pathInfo, err := client.GetPathInfo(ciFileName) if err != nil { return nil, err } - if !exists { + if len(pathInfo) == 0 { return nil, nil } diff --git a/internal/pkg/plugin/gitlabci/java/update.go b/internal/pkg/plugin/gitlabci/java/update.go index 32fea9d32..185275f53 100644 --- a/internal/pkg/plugin/gitlabci/java/update.go +++ b/internal/pkg/plugin/gitlabci/java/update.go @@ -46,7 +46,7 @@ func Update(options map[string]interface{}) (map[string]interface{}, error) { } // the only difference between "Create" and "Update" - if err = client.UpdateFiles(commitInfo); err != nil { + if _, err = client.PushFiles(commitInfo, true); err != nil { return nil, err } diff --git a/internal/pkg/pluginengine/change.go b/internal/pkg/pluginengine/change.go index 89a9f0a87..16f7d37d0 100644 --- a/internal/pkg/pluginengine/change.go +++ b/internal/pkg/pluginengine/change.go @@ -30,13 +30,6 @@ func (c *Change) String() string { c.ActionName, c.Tool.Name, c.Tool.InstanceID) } -type CommandType string - -const ( - CommandApply CommandType = "apply" - CommandDelete CommandType = "delete" -) - // GetChangesForApply takes "State Manager" & "Config" then do some calculate and return a Plan. // All actions should be executed is included in this Plan.changes. func GetChangesForApply(smgr statemanager.Manager, cfg *configmanager.Config) ([]*Change, error) { diff --git a/internal/pkg/pluginengine/cmd_apply.go b/internal/pkg/pluginengine/cmd_apply.go index c81204e7f..e08d6b8d4 100644 --- a/internal/pkg/pluginengine/cmd_apply.go +++ b/internal/pkg/pluginengine/cmd_apply.go @@ -8,6 +8,7 @@ import ( "github.com/devstream-io/devstream/internal/pkg/pluginmanager" "github.com/devstream-io/devstream/internal/pkg/statemanager" "github.com/devstream-io/devstream/pkg/util/file" + "github.com/devstream-io/devstream/pkg/util/interact" "github.com/devstream-io/devstream/pkg/util/log" ) @@ -48,8 +49,8 @@ func Apply(configFile string, continueDirectly bool) error { } if !continueDirectly { - userInput := readUserInput() - if userInput == "n" { + continued := interact.AskUserIfContinue(askUserIfContinue) + if !continued { os.Exit(0) } } diff --git a/internal/pkg/pluginengine/cmd_delete.go b/internal/pkg/pluginengine/cmd_delete.go index b13401ea5..628ed2981 100644 --- a/internal/pkg/pluginengine/cmd_delete.go +++ b/internal/pkg/pluginengine/cmd_delete.go @@ -9,6 +9,7 @@ import ( "github.com/devstream-io/devstream/internal/pkg/pluginmanager" "github.com/devstream-io/devstream/internal/pkg/statemanager" "github.com/devstream-io/devstream/pkg/util/file" + "github.com/devstream-io/devstream/pkg/util/interact" "github.com/devstream-io/devstream/pkg/util/log" ) @@ -52,8 +53,8 @@ func Remove(configFile string, continueDirectly bool, isForceDelete bool) error } if !continueDirectly { - userInput := readUserInput() - if userInput == "n" { + continued := interact.AskUserIfContinue(askUserIfContinue) + if !continued { os.Exit(0) } } diff --git a/internal/pkg/pluginengine/cmd_destroy.go b/internal/pkg/pluginengine/cmd_destroy.go index 10aa1f705..4bba4aa67 100644 --- a/internal/pkg/pluginengine/cmd_destroy.go +++ b/internal/pkg/pluginengine/cmd_destroy.go @@ -8,6 +8,7 @@ import ( "github.com/devstream-io/devstream/internal/pkg/configmanager" "github.com/devstream-io/devstream/internal/pkg/statemanager" "github.com/devstream-io/devstream/pkg/util/file" + "github.com/devstream-io/devstream/pkg/util/interact" "github.com/devstream-io/devstream/pkg/util/log" ) @@ -45,8 +46,8 @@ func Destroy(configFile string, continueDirectly bool, isForceDestroy bool) erro } if !continueDirectly { - userInput := readUserInput() - if userInput == "n" { + continued := interact.AskUserIfContinue(askUserIfContinue) + if !continued { os.Exit(0) } } diff --git a/internal/pkg/pluginengine/const.go b/internal/pkg/pluginengine/const.go new file mode 100644 index 000000000..9fa4e09c6 --- /dev/null +++ b/internal/pkg/pluginengine/const.go @@ -0,0 +1,10 @@ +package pluginengine + +type CommandType string + +const ( + CommandApply CommandType = "apply" + CommandDelete CommandType = "delete" + + askUserIfContinue string = "Continue? [y/n]" +) diff --git a/internal/pkg/plugininstaller/ci/ci.go b/internal/pkg/plugininstaller/ci/ci.go deleted file mode 100644 index 9100f8a69..000000000 --- a/internal/pkg/plugininstaller/ci/ci.go +++ /dev/null @@ -1,15 +0,0 @@ -package ci - -type ciRepoType string - -const ( - defaultBranch = "feat-repo-ci-update" - defaultCommitMsg = "update ci config" - ciJenkinsConfigLocation = "Jenkinsfile" - ciGitHubWorkConfigLocation = ".github/workflows" - ciGitLabConfigLocation = ".gitlab-ci.yml" - ciJenkinsType ciRepoType = "jenkins" - ciGitLabType ciRepoType = "gitlab" - ciGitHubType ciRepoType = "github" - deleteCommitMsg = "delete ci files" -) diff --git a/internal/pkg/plugininstaller/ci/installer.go b/internal/pkg/plugininstaller/ci/installer.go index 1f55017e4..c5ea18c8f 100644 --- a/internal/pkg/plugininstaller/ci/installer.go +++ b/internal/pkg/plugininstaller/ci/installer.go @@ -9,6 +9,13 @@ import ( "github.com/devstream-io/devstream/internal/pkg/plugininstaller" ) +const ( + createCommitMsg = "update ci config" + deleteCommitMsg = "delete ci files" + // this variable is only used for github to fork a branch and create pr + defaultBranch = "feat-repo-ci-update" +) + func PushCIFiles(options plugininstaller.RawOptions) error { opts, err := NewOptions(options) if err != nil { @@ -25,10 +32,10 @@ func PushCIFiles(options plugininstaller.RawOptions) error { return err } //4. push ci files to git repo - _, err = gitClient.PushLocalFileToRepo(&git.CommitInfo{ - CommitMsg: defaultCommitMsg, - CommitBranch: defaultBranch, + _, err = gitClient.PushFiles(&git.CommitInfo{ + CommitMsg: createCommitMsg, GitFileMap: gitMap, + CommitBranch: defaultBranch, }, true) return err } @@ -53,8 +60,9 @@ func DeleteCIFiles(options plugininstaller.RawOptions) error { } //3. delete ci files from git repo commitInfo := &git.CommitInfo{ - GitFileMap: gitMap, - CommitMsg: deleteCommitMsg, + CommitMsg: deleteCommitMsg, + GitFileMap: gitMap, + CommitBranch: opts.ProjectRepo.Branch, } return gitClient.DeleteFiles(commitInfo) } diff --git a/internal/pkg/plugininstaller/ci/option.go b/internal/pkg/plugininstaller/ci/option.go index e0d3822a8..34f35e156 100644 --- a/internal/pkg/plugininstaller/ci/option.go +++ b/internal/pkg/plugininstaller/ci/option.go @@ -9,15 +9,17 @@ import ( "github.com/mitchellh/mapstructure" "github.com/devstream-io/devstream/internal/pkg/plugininstaller" + "github.com/devstream-io/devstream/internal/pkg/plugininstaller/ci/server" "github.com/devstream-io/devstream/internal/pkg/plugininstaller/common" "github.com/devstream-io/devstream/pkg/util/file" + "github.com/devstream-io/devstream/pkg/util/log" "github.com/devstream-io/devstream/pkg/util/scm/git" "github.com/devstream-io/devstream/pkg/util/template" "github.com/devstream-io/devstream/pkg/util/types" ) type CIConfig struct { - Type ciRepoType `validate:"oneof=jenkins github gitlab" mapstructure:"type"` + Type server.CIServerType `validate:"oneof=jenkins github gitlab" mapstructure:"type"` LocalPath string `mapstructure:"localPath"` RemoteURL string `mapstructure:"remoteURL"` Content string `mapstructure:"content"` @@ -37,19 +39,26 @@ func NewOptions(options plugininstaller.RawOptions) (*Options, error) { return &opts, nil } +func (c *CIConfig) CIClient() (ciClient server.CIServerOptions) { + return server.NewCIServer(c.Type) +} + // getCIFile will generate ci files by config func (opt *Options) buildGitMap() (gitMap git.GitFileContentMap, err error) { ciConfig := opt.CIConfig switch { case ciConfig.LocalPath != "": - gitMap, err = ciConfig.getFromLocation(opt.ProjectRepo.Repo) + gitMap, err = ciConfig.getFromLocal(opt.ProjectRepo.Repo) case ciConfig.RemoteURL != "": gitMap, err = ciConfig.getFromURL(opt.ProjectRepo.Repo) case ciConfig.Content != "": gitMap, err = ciConfig.getFromContent(opt.ProjectRepo.Repo) } if len(gitMap) == 0 { - return nil, errors.New("can't get valid Jenkinsfile, please check your config") + if err != nil { + log.Warnf("ci get file failed: %+v", err) + } + return nil, errors.New("can't get valid ci file, please check your config") } return gitMap, err } @@ -60,29 +69,28 @@ func (c *CIConfig) getFromURL(appName string) (git.GitFileContentMap, error) { if err != nil { return nil, err } - fileName := getGitNameFunc(c.Type)("", path.Base(c.RemoteURL)) + fileName := c.CIClient().GetGitNameFunc()("", path.Base(c.RemoteURL)) gitFileMap[fileName] = []byte(content) return gitFileMap, nil } -func (c *CIConfig) getFromLocation(appName string) (git.GitFileContentMap, error) { +func (c *CIConfig) getFromLocal(appName string) (git.GitFileContentMap, error) { gitFileMap := make(git.GitFileContentMap) info, err := os.Stat(c.LocalPath) if err != nil { return nil, err } + + ciClient := c.CIClient() // process dir if info.IsDir() { return file.WalkDir( - c.LocalPath, filterCIFilesFunc(c.Type), - getGitNameFunc(c.Type), processCIFilesFunc(appName, c.Vars), + c.LocalPath, ciClient.FilterCIFilesFunc(), + ciClient.GetGitNameFunc(), processCIFilesFunc(appName, c.Vars), ) } // process file - gitFilePath := getCIFilePath(c.Type) - if c.Type == ciGitHubWorkConfigLocation { - gitFilePath = filepath.Join(gitFilePath, filepath.Base(c.LocalPath)) - } + gitFilePath := ciClient.CIFilePath(filepath.Base(c.LocalPath)) content, err := template.New().FromLocalFile(c.LocalPath).SetDefaultRender(appName, c.Vars).Render() if err != nil { return nil, err @@ -91,9 +99,13 @@ func (c *CIConfig) getFromLocation(appName string) (git.GitFileContentMap, error return gitFileMap, nil } -func (c *CIConfig) getFromContent(content string) (git.GitFileContentMap, error) { +func (c *CIConfig) getFromContent(appName string) (git.GitFileContentMap, error) { gitFileMap := make(git.GitFileContentMap) - gitFileMap[getCIFilePath(c.Type)] = []byte(content) + content, err := template.New().FromContent(c.Content).SetDefaultRender(appName, c.Vars).Render() + if err != nil { + return nil, err + } + gitFileMap[c.CIClient().CIFilePath(appName)] = []byte(content) return gitFileMap, nil } diff --git a/internal/pkg/plugininstaller/ci/option_test.go b/internal/pkg/plugininstaller/ci/option_test.go new file mode 100644 index 000000000..1f067c5d9 --- /dev/null +++ b/internal/pkg/plugininstaller/ci/option_test.go @@ -0,0 +1,220 @@ +package ci + +import ( + "fmt" + "net/http" + "os" + "path" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/ghttp" + + "github.com/devstream-io/devstream/internal/pkg/plugininstaller" + "github.com/devstream-io/devstream/internal/pkg/plugininstaller/ci/server" + "github.com/devstream-io/devstream/internal/pkg/plugininstaller/common" +) + +var _ = Describe("Options struct", func() { + Context("NewOptions method", func() { + var ( + rawOptions plugininstaller.RawOptions + ) + When("options is valid", func() { + BeforeEach(func() { + rawOptions = plugininstaller.RawOptions{ + "ci": map[string]interface{}{ + "type": "gitlab", + "content": "test", + }, + } + }) + It("should not raise error", func() { + _, err := NewOptions(rawOptions) + Expect(err).Error().ShouldNot(HaveOccurred()) + }) + }) + }) + + Context("buildGitMap method", func() { + var ( + opts *Options + repo *common.Repo + ) + BeforeEach(func() { + opts = &Options{} + repo = &common.Repo{ + Owner: "test", + Repo: "test_repo", + Branch: "test_branch", + RepoType: "gitlab", + } + opts.ProjectRepo = repo + }) + When("all ci config is empty", func() { + BeforeEach(func() { + opts.CIConfig = &CIConfig{} + }) + It("should raise error", func() { + _, err := opts.buildGitMap() + Expect(err).Error().Should(HaveOccurred()) + }) + }) + When("LocalPath field is setted with github ci files", func() { + var ( + localDir, localFile string + testContent []byte + ) + BeforeEach(func() { + tempDir := GinkgoT().TempDir() + localDir = fmt.Sprintf("%s/%s", tempDir, ".github/workflows") + err := os.MkdirAll(localDir, os.ModePerm) + Expect(err).Error().ShouldNot(HaveOccurred()) + tempFile, err := os.CreateTemp(localDir, "testFile") + Expect(err).Error().ShouldNot(HaveOccurred()) + localFile = tempFile.Name() + testContent = []byte("_test") + err = os.WriteFile(localFile, testContent, 0755) + Expect(err).Error().ShouldNot(HaveOccurred()) + }) + When("LocalPath is directory", func() { + BeforeEach(func() { + opts.CIConfig = &CIConfig{ + Type: "github", + LocalPath: localDir, + } + }) + It("should get all files content", func() { + gitMap, err := opts.buildGitMap() + Expect(err).Error().ShouldNot(HaveOccurred()) + Expect(gitMap).ShouldNot(BeEmpty()) + expectedKey := fmt.Sprintf("%s/%s", ".github/workflows", path.Base(localFile)) + v, ok := gitMap[expectedKey] + Expect(ok).Should(BeTrue()) + Expect(v).Should(Equal(testContent)) + }) + }) + When("localPath is file", func() { + BeforeEach(func() { + opts.CIConfig = &CIConfig{ + Type: "github", + LocalPath: localFile, + } + }) + It("should get file content", func() { + gitMap, err := opts.buildGitMap() + Expect(err).Error().ShouldNot(HaveOccurred()) + Expect(gitMap).ShouldNot(BeEmpty()) + expectedKey := fmt.Sprintf("%s/%s", ".github/workflows", path.Base(localFile)) + v, ok := gitMap[expectedKey] + Expect(ok).Should(BeTrue()) + Expect(v).Should(Equal(testContent)) + }) + }) + }) + When("Content field is setted", func() { + var ( + testContent string + ) + BeforeEach(func() { + testContent = "testJenkins" + opts.CIConfig = &CIConfig{ + Type: "jenkins", + Content: testContent, + } + }) + It("should return gitmap", func() { + gitMap, err := opts.buildGitMap() + Expect(err).Error().ShouldNot(HaveOccurred()) + Expect(len(gitMap)).Should(Equal(1)) + v, ok := gitMap["Jenkinsfile"] + Expect(ok).Should(BeTrue()) + Expect(v).Should(Equal([]byte(testContent))) + }) + }) + + When("RemoteURL field is setted", func() { + var ( + templateVal string + s *ghttp.Server + ) + BeforeEach(func() { + s = ghttp.NewServer() + testContent := "testGitlabCI [[ .App ]]" + templateVal = "template variable" + s.RouteToHandler("GET", "/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, testContent) + }) + opts.CIConfig = &CIConfig{ + Type: "gitlab", + RemoteURL: s.URL(), + Vars: map[string]interface{}{ + "App": templateVal, + }, + } + }) + AfterEach(func() { + s.Close() + }) + It("should get gitmap", func() { + gitMap, err := opts.buildGitMap() + Expect(err).Error().ShouldNot(HaveOccurred()) + Expect(len(gitMap)).Should(Equal(1)) + v, ok := gitMap[".gitlab-ci.yml"] + Expect(ok).Should(BeTrue()) + Expect(string(v)).Should(Equal(fmt.Sprintf("testGitlabCI %s", templateVal))) + }) + }) + }) + + Context("FillDefaultValue method", func() { + var ( + defaultOpts, opts *Options + ) + BeforeEach(func() { + opts = &Options{} + defaultCIConfig := &CIConfig{ + Type: "github", + RemoteURL: "http://www.test.com", + } + defaultRepo := &common.Repo{ + Owner: "test", + Repo: "test_repo", + Branch: "test_branch", + RepoType: "gitlab", + } + defaultOpts = &Options{ + CIConfig: defaultCIConfig, + ProjectRepo: defaultRepo, + } + }) + When("ci config and repo are all empty", func() { + It("should set default value", func() { + opts.FillDefaultValue(defaultOpts) + Expect(opts.CIConfig).ShouldNot(BeNil()) + Expect(opts.ProjectRepo).ShouldNot(BeNil()) + Expect(opts.CIConfig.RemoteURL).Should(Equal("http://www.test.com")) + Expect(opts.ProjectRepo.Repo).Should(Equal("test_repo")) + }) + }) + When("ci config and repo has some value", func() { + BeforeEach(func() { + opts.CIConfig = &CIConfig{ + RemoteURL: "http://exist.com", + } + opts.ProjectRepo = &common.Repo{ + Branch: "new_branch", + } + }) + It("should update empty value", func() { + opts.FillDefaultValue(defaultOpts) + Expect(opts.CIConfig).ShouldNot(BeNil()) + Expect(opts.ProjectRepo).ShouldNot(BeNil()) + Expect(opts.CIConfig.RemoteURL).Should(Equal("http://exist.com")) + Expect(opts.CIConfig.Type).Should(Equal(server.CIServerType("github"))) + Expect(opts.ProjectRepo.Branch).Should(Equal("new_branch")) + Expect(opts.ProjectRepo.Repo).Should(Equal("test_repo")) + }) + }) + }) +}) diff --git a/internal/pkg/plugininstaller/ci/server/githubci.go b/internal/pkg/plugininstaller/ci/server/githubci.go new file mode 100644 index 000000000..d3ccd249d --- /dev/null +++ b/internal/pkg/plugininstaller/ci/server/githubci.go @@ -0,0 +1,45 @@ +package server + +import ( + "path/filepath" + "strings" + + "github.com/devstream-io/devstream/pkg/util/file" +) + +const ( + ciGitHubType CIServerType = "github" + ciGitHubWorkConfigLocation string = ".github/workflows" +) + +type GitHubCI struct { +} + +func (g *GitHubCI) Type() CIServerType { + return ciGitHubType +} + +func (g *GitHubCI) CIFilePath(subFilename ...string) string { + // if subFilename is empty, return dir(.github/workflows) + if len(subFilename) == 0 { + return ciGitHubWorkConfigLocation + } + // else return dir + subFilename + return filepath.Join(ciGitHubWorkConfigLocation, filepath.Base(subFilename[0])) +} + +func (g *GitHubCI) FilterCIFilesFunc() file.DirFIleFilterFunc { + return func(filePath string, isDir bool) bool { + // not process dir + if isDir { + return false + } + return strings.Contains(filePath, "workflows") + } +} + +func (g *GitHubCI) GetGitNameFunc() file.DirFileNameFunc { + return func(filePath, walkDir string) string { + return g.CIFilePath(filePath) + } +} diff --git a/internal/pkg/plugininstaller/ci/server/gitlabci.go b/internal/pkg/plugininstaller/ci/server/gitlabci.go new file mode 100644 index 000000000..8ed59d63a --- /dev/null +++ b/internal/pkg/plugininstaller/ci/server/gitlabci.go @@ -0,0 +1,39 @@ +package server + +import ( + "path/filepath" + + "github.com/devstream-io/devstream/pkg/util/file" +) + +const ( + ciGitLabType CIServerType = "gitlab" + ciGitLabConfigLocation string = ".gitlab-ci.yml" +) + +type GitLabCI struct { +} + +func (g *GitLabCI) Type() CIServerType { + return ciGitLabType +} + +func (g *GitLabCI) CIFilePath(_ ...string) string { + return ciGitLabConfigLocation +} + +func (g *GitLabCI) FilterCIFilesFunc() file.DirFIleFilterFunc { + return func(filePath string, isDir bool) bool { + // not process dir + if isDir { + return false + } + return filepath.Base(filePath) == g.CIFilePath() + } +} + +func (g *GitLabCI) GetGitNameFunc() file.DirFileNameFunc { + return func(filePath, walkDir string) string { + return g.CIFilePath() + } +} diff --git a/internal/pkg/plugininstaller/ci/server/jenkinsci.go b/internal/pkg/plugininstaller/ci/server/jenkinsci.go new file mode 100644 index 000000000..cc60b1d25 --- /dev/null +++ b/internal/pkg/plugininstaller/ci/server/jenkinsci.go @@ -0,0 +1,35 @@ +package server + +import "github.com/devstream-io/devstream/pkg/util/file" + +const ( + ciJenkinsType CIServerType = "jenkins" + ciJenkinsConfigLocation string = "Jenkinsfile" +) + +type JenkinsCI struct { +} + +func (j *JenkinsCI) Type() CIServerType { + return ciJenkinsType +} + +func (j *JenkinsCI) CIFilePath(_ ...string) string { + return ciJenkinsConfigLocation +} + +func (j *JenkinsCI) FilterCIFilesFunc() file.DirFIleFilterFunc { + return func(filePath string, isDir bool) bool { + // not process dir + if isDir { + return false + } + return filePath == ciJenkinsConfigLocation + } +} + +func (j *JenkinsCI) GetGitNameFunc() file.DirFileNameFunc { + return func(filePath, walkDir string) string { + return j.CIFilePath() + } +} diff --git a/internal/pkg/plugininstaller/ci/server/server.go b/internal/pkg/plugininstaller/ci/server/server.go new file mode 100644 index 000000000..e23e7648c --- /dev/null +++ b/internal/pkg/plugininstaller/ci/server/server.go @@ -0,0 +1,34 @@ +package server + +import "github.com/devstream-io/devstream/pkg/util/file" + +type CIServerType string + +type CIServerOptions interface { + // Type return ci type + Type() CIServerType + // CIFilePath returns the file path of ci config file + // gitlab and jenkins is just a file, so we can just use filename + // but GitHub use directory, we should process this situation + // for GitHub: return ".github/workflows" or ".github/workflows/subFilename" + // for gitlab, jenkins: will ignore subFilename param + CIFilePath(subFilename ...string) string + // FilterCIFilesFunc returns a filter function to select ci config file + FilterCIFilesFunc() file.DirFIleFilterFunc + // GetGitNameFunc returns a function to transform file path to git name of ci config file + GetGitNameFunc() file.DirFileNameFunc +} + +func NewCIServer(ciType CIServerType) CIServerOptions { + // there are no validation for ciType + // because we have already validated it by `validate` flag in CIConfig.Type + switch ciType { + case ciGitLabType: + return &GitLabCI{} + case ciGitHubType: + return &GitHubCI{} + case ciJenkinsType: + return &JenkinsCI{} + } + return nil +} diff --git a/internal/pkg/plugininstaller/ci/server/server_suite_test.go b/internal/pkg/plugininstaller/ci/server/server_suite_test.go new file mode 100644 index 000000000..c97b8c94c --- /dev/null +++ b/internal/pkg/plugininstaller/ci/server/server_suite_test.go @@ -0,0 +1,13 @@ +package server_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestCommon(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "PluginInstaller Ci Server Suite") +} diff --git a/internal/pkg/plugininstaller/ci/server/server_test.go b/internal/pkg/plugininstaller/ci/server/server_test.go new file mode 100644 index 000000000..dfd25f6f0 --- /dev/null +++ b/internal/pkg/plugininstaller/ci/server/server_test.go @@ -0,0 +1,71 @@ +package server + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ci walkDir methods", func() { + type testCase struct { + filePath string + ciType CIServerType + isDir bool + } + var ( + testCases []*testCase + testCaseData *testCase + ) + Context("filterCIFilesFunc func", func() { + When("input file not not right", func() { + BeforeEach(func() { + testCases = []*testCase{ + {"Jenkinsfile", CIServerType("gitlab"), false}, + {".gitlab.ci", CIServerType("jenkins"), false}, + {"Jenkinsfile", CIServerType("jenkins"), true}, + {"workflows/pr.yaml", CIServerType("jenkins"), false}, + } + }) + It("should return false", func() { + for _, tt := range testCases { + Expect(NewCIServer(tt.ciType).FilterCIFilesFunc()(tt.filePath, tt.isDir)).Should(BeFalse()) + } + }) + }) + When("input file is valid", func() { + BeforeEach(func() { + testCases = []*testCase{ + {"Jenkinsfile", CIServerType("jenkins"), false}, + {".gitlab-ci.yml", CIServerType("gitlab"), false}, + {"workflows/pr.yaml", CIServerType("github"), false}, + {"workflows/pr2.yaml", CIServerType("github"), false}, + } + }) + It("should return true", func() { + for _, tt := range testCases { + Expect(NewCIServer(tt.ciType).FilterCIFilesFunc()(tt.filePath, tt.isDir)).Should(BeTrue()) + } + }) + }) + }) + + Context("getGitNameFunc func", func() { + When("ciType is github", func() { + BeforeEach(func() { + testCaseData = &testCase{"workflows/pr.yaml", CIServerType("github"), false} + }) + It("should return github workflows path", func() { + result := NewCIServer(testCaseData.ciType).GetGitNameFunc()(testCaseData.filePath, "workflows") + Expect(result).Should(Equal(".github/workflows/pr.yaml")) + }) + }) + When("ciType is others", func() { + BeforeEach(func() { + testCaseData = &testCase{"work/Jenkinsfile", CIServerType("jenkins"), false} + }) + It("should return github workflows path", func() { + result := NewCIServer(testCaseData.ciType).GetGitNameFunc()(testCaseData.filePath, "") + Expect(result).Should(Equal("Jenkinsfile")) + }) + }) + }) +}) diff --git a/internal/pkg/plugininstaller/ci/state.go b/internal/pkg/plugininstaller/ci/state.go index df9a40214..f1320bbea 100644 --- a/internal/pkg/plugininstaller/ci/state.go +++ b/internal/pkg/plugininstaller/ci/state.go @@ -3,7 +3,9 @@ package ci import ( "github.com/devstream-io/devstream/internal/pkg/plugininstaller" "github.com/devstream-io/devstream/internal/pkg/statemanager" + "github.com/devstream-io/devstream/pkg/util/log" "github.com/devstream-io/devstream/pkg/util/scm" + "github.com/devstream-io/devstream/pkg/util/scm/git" ) func GetCIFileStatus(options plugininstaller.RawOptions) (statemanager.ResourceState, error) { @@ -11,22 +13,43 @@ func GetCIFileStatus(options plugininstaller.RawOptions) (statemanager.ResourceS if err != nil { return nil, err } - fileLocation := getCIFilePath(opts.CIConfig.Type) + // init scm client client, err := scm.NewClient(opts.ProjectRepo.BuildRepoInfo()) if err != nil { return nil, err } - gitFileInfo, err := client.GetLocationInfo(fileLocation) + + // get local file info + gitMap, err := opts.buildGitMap() if err != nil { + log.Debugf("ci state get gitMap failed: %+v", err) return nil, err } + statusMap := make(map[string]interface{}) - for _, item := range gitFileInfo { - statusMap[item.Path] = map[string]string{ - "sha": item.SHA, - "path": item.Path, - "branch": item.Branch, + for scmPath, content := range gitMap { + localFileSHA := git.CalculateLocalFileSHA(content) + // get remote file status + statusMap[scmPath] = map[string]interface{}{ + "localSHA": localFileSHA, + "scm": getSCMFileStatus(client, scmPath), } + } return statusMap, nil } + +func getSCMFileStatus(client scm.ClientOperation, scmPath string) (scmFileStatus []map[string]string) { + gitFileInfos, err := client.GetPathInfo(scmPath) + if err != nil { + log.Debugf("ci status get location info failed: %+v", err) + return scmFileStatus + } + for _, fileStatus := range gitFileInfos { + scmFileStatus = append(scmFileStatus, map[string]string{ + "scmSHA": fileStatus.SHA, + "scmBranch": fileStatus.Branch, + }) + } + return scmFileStatus +} diff --git a/internal/pkg/plugininstaller/ci/utils.go b/internal/pkg/plugininstaller/ci/utils.go index 8570f84c8..c74003905 100644 --- a/internal/pkg/plugininstaller/ci/utils.go +++ b/internal/pkg/plugininstaller/ci/utils.go @@ -2,41 +2,11 @@ package ci import ( "os" - "path/filepath" - "strings" "github.com/devstream-io/devstream/pkg/util/file" "github.com/devstream-io/devstream/pkg/util/template" ) -// gitlab and jenkins is just a file, so we can just use filename -// github use directory, we shoud process this situation -func getCIFilePath(ciType ciRepoType) string { - switch ciType { - case ciGitLabType: - return ciGitLabConfigLocation - case ciGitHubType: - return ciGitHubWorkConfigLocation - case ciJenkinsType: - return ciJenkinsConfigLocation - } - return "" -} - -func filterCIFilesFunc(ciType ciRepoType) file.DirFIleFilterFunc { - ciFileName := getCIFilePath(ciType) - return func(filePath string, isDir bool) bool { - // not process dir - if isDir { - return false - } - if ciType == ciGitHubType { - return strings.Contains(filePath, "workflows") - } - return filepath.Base(filePath) == ciFileName - } -} - func processCIFilesFunc(templateName string, vars map[string]interface{}) file.DirFileProcessFunc { return func(filePath string) ([]byte, error) { if len(vars) == 0 { @@ -49,14 +19,3 @@ func processCIFilesFunc(templateName string, vars map[string]interface{}) file.D return []byte(renderContent), nil } } - -func getGitNameFunc(ciType ciRepoType) file.DirFileNameFunc { - ciFilePath := getCIFilePath(ciType) - return func(filePath, walkDir string) string { - fileBaseName := filepath.Base(filePath) - if ciType == ciGitHubType { - return filepath.Join(ciFilePath, fileBaseName) - } - return ciFilePath - } -} diff --git a/internal/pkg/plugininstaller/ci/utils_test.go b/internal/pkg/plugininstaller/ci/utils_test.go index de81c8c6d..3ac0b7a76 100644 --- a/internal/pkg/plugininstaller/ci/utils_test.go +++ b/internal/pkg/plugininstaller/ci/utils_test.go @@ -7,100 +7,37 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("ci walkDir methods", func() { - type testCase struct { - filePath string - ciType ciRepoType - isDir bool - } +var _ = Describe("processCIFilesFunc func", func() { var ( - testCases []*testCase - testCaseData *testCase + tempFileLoc string + testContent []byte ) - Context("filterCIFilesFunc func", func() { - When("input file not not right", func() { - BeforeEach(func() { - testCases = []*testCase{ - {"Jenkinsfile", ciRepoType("gitlab"), false}, - {".gitlab.ci", ciRepoType("jenkins"), false}, - {"Jenkinsfile", ciRepoType("jenkins"), true}, - {"workflows/pr.yaml", ciRepoType("jenkins"), false}, - } - }) - It("should return false", func() { - for _, tt := range testCases { - Expect(filterCIFilesFunc(tt.ciType)(tt.filePath, tt.isDir)).Should(BeFalse()) - } - }) - }) - When("input file is valid", func() { - BeforeEach(func() { - testCases = []*testCase{ - {"Jenkinsfile", ciRepoType("jenkins"), false}, - {".gitlab-ci.yml", ciRepoType("gitlab"), false}, - {"workflows/pr.yaml", ciRepoType("github"), false}, - {"workflows/pr2.yaml", ciRepoType("github"), false}, - } - }) - It("should return true", func() { - for _, tt := range testCases { - Expect(filterCIFilesFunc(tt.ciType)(tt.filePath, tt.isDir)).Should(BeTrue()) - } - }) - }) + BeforeEach(func() { + tempDir := GinkgoT().TempDir() + tempFile, err := os.CreateTemp(tempDir, "testFile") + Expect(err).Error().ShouldNot(HaveOccurred()) + tempFileLoc = tempFile.Name() }) - Context("processCIFilesFunc func", func() { - var ( - tempFileLoc string - testContent []byte - ) + When("file is right", func() { BeforeEach(func() { - tempDir := GinkgoT().TempDir() - tempFile, err := os.CreateTemp(tempDir, "testFile") + testContent = []byte("[[ .Name ]]_test") + err := os.WriteFile(tempFileLoc, testContent, 0755) Expect(err).Error().ShouldNot(HaveOccurred()) - tempFileLoc = tempFile.Name() - }) - When("file is right", func() { - BeforeEach(func() { - testContent = []byte("[[ .Name ]]_test") - err := os.WriteFile(tempFileLoc, testContent, 0755) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - It("should work as expected", func() { - result, err := processCIFilesFunc("test", map[string]interface{}{ - "Name": "devstream", - })(tempFileLoc) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(result).Should(Equal([]byte("devstream_test"))) - }) }) - When("file is not exist", func() { - It("should return error", func() { - _, err := processCIFilesFunc("test", map[string]interface{}{ - "Name": "devstream", - })("not_exist_file") - Expect(err).Error().Should(HaveOccurred()) - }) + It("should work as expected", func() { + result, err := processCIFilesFunc("test", map[string]interface{}{ + "Name": "devstream", + })(tempFileLoc) + Expect(err).Error().ShouldNot(HaveOccurred()) + Expect(result).Should(Equal([]byte("devstream_test"))) }) }) - Context("getGitNameFunc func", func() { - When("ciType is github", func() { - BeforeEach(func() { - testCaseData = &testCase{"workflows/pr.yaml", ciRepoType("github"), false} - }) - It("should return github workflows path", func() { - result := getGitNameFunc(testCaseData.ciType)(testCaseData.filePath, "workflows") - Expect(result).Should(Equal(".github/workflows/pr.yaml")) - }) - }) - When("ciType is others", func() { - BeforeEach(func() { - testCaseData = &testCase{"work/Jenkinsfile", ciRepoType("jenkins"), false} - }) - It("should return github workflows path", func() { - result := getGitNameFunc(testCaseData.ciType)(testCaseData.filePath, "") - Expect(result).Should(Equal("Jenkinsfile")) - }) + When("file is not exist", func() { + It("should return error", func() { + _, err := processCIFilesFunc("test", map[string]interface{}{ + "Name": "devstream", + })("not_exist_file") + Expect(err).Error().Should(HaveOccurred()) }) }) }) diff --git a/internal/pkg/plugininstaller/ci/validate.go b/internal/pkg/plugininstaller/ci/validate.go index d90428484..b5b9626ee 100644 --- a/internal/pkg/plugininstaller/ci/validate.go +++ b/internal/pkg/plugininstaller/ci/validate.go @@ -35,13 +35,6 @@ func Validate(options plugininstaller.RawOptions) (plugininstaller.RawOptions, e } else if config.Content == "" { return nil, errors.New("ci.locaPath, ci.remoteURL, ci.content can't all be empty at the same time") } - - if config.Type == ciGitHubType && opts.ProjectRepo.RepoType == "gitlab" { - return nil, errors.New("github ci doesn't support gitlab") - } - if config.Type == ciGitLabType && opts.ProjectRepo.RepoType == "github" { - return nil, errors.New("gitlab ci doesn't support github") - } return options, nil } diff --git a/internal/pkg/plugininstaller/ci/validate_test.go b/internal/pkg/plugininstaller/ci/validate_test.go index d9e163c84..7da7ecd6e 100644 --- a/internal/pkg/plugininstaller/ci/validate_test.go +++ b/internal/pkg/plugininstaller/ci/validate_test.go @@ -5,6 +5,7 @@ import ( . "github.com/onsi/gomega" "github.com/devstream-io/devstream/internal/pkg/plugininstaller/ci" + "github.com/devstream-io/devstream/internal/pkg/plugininstaller/common" ) var _ = Describe("Validate func", func() { @@ -60,6 +61,23 @@ var _ = Describe("Validate func", func() { _, err = ci.Validate(repoCiConflictOption) Expect(err).Should(HaveOccurred()) + ciTypeNotExistOptions := map[string]any{ + "ci": map[string]any{ + "localPath": "workflows/Jenkinsfile", + "type": "gg", + }, + "projectRepo": map[string]any{ + "baseURL": "http://127.0.0.1:30020", + "branch": "main", + "org": "", + "owner": "test_user", + "repo": "test", + "repoType": "gitlab", + }, + } + _, err = ci.Validate(ciTypeNotExistOptions) + Expect(err).Should(HaveOccurred()) + }) }) When("options is valid", func() { @@ -99,3 +117,30 @@ var _ = Describe("Validate func", func() { }) }) }) + +var _ = Describe("SetDefaultConfig func", func() { + var defaultOpts *ci.Options + BeforeEach(func() { + defaultCIConfig := &ci.CIConfig{ + Type: "github", + RemoteURL: "http://www.test.com", + } + defaultRepo := &common.Repo{ + Owner: "test", + Repo: "test_repo", + Branch: "test_branch", + RepoType: "gitlab", + } + defaultOpts = &ci.Options{ + CIConfig: defaultCIConfig, + ProjectRepo: defaultRepo, + } + }) + It("should work normal", func() { + defaultFunc := ci.SetDefaultConfig(defaultOpts) + rawOptions := map[string]interface{}{} + opts, err := defaultFunc(rawOptions) + Expect(err).Error().ShouldNot(HaveOccurred()) + Expect(len(opts)).Should(Equal(2)) + }) +}) diff --git a/internal/pkg/plugininstaller/common/repo_test.go b/internal/pkg/plugininstaller/common/repo_test.go index f6b766e48..16d3487a2 100644 --- a/internal/pkg/plugininstaller/common/repo_test.go +++ b/internal/pkg/plugininstaller/common/repo_test.go @@ -1,6 +1,8 @@ package common import ( + "fmt" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -46,6 +48,68 @@ var _ = Describe("Repo Struct", func() { Expect(owner).Should(Equal(org)) }) }) + + Context("getBranch method", func() { + When("repo is gitlab and branch is empty", func() { + BeforeEach(func() { + repo.Branch = "" + repo.RepoType = "gitlab" + }) + It("should return master branch", func() { + branch := repo.getBranch() + Expect(branch).Should(Equal("master")) + }) + }) + When("repo is github and branch is empty", func() { + BeforeEach(func() { + repo.Branch = "" + repo.RepoType = "github" + }) + It("should return main branch", func() { + branch := repo.getBranch() + Expect(branch).Should(Equal("main")) + }) + }) + }) + + Context("getRepoDownloadURL method", func() { + It("should return url", func() { + githubURL := repo.getRepoDownloadURL() + Expect(githubURL).Should(Equal("https://codeload.github.com/test_org/test_repo/zip/refs/heads/test_branch")) + }) + }) + + Context("BuildURL method", func() { + When("repo is empty", func() { + BeforeEach(func() { + repo.RepoType = "not_exist" + }) + It("should return empty url", func() { + url := repo.BuildURL() + Expect(url).Should(BeEmpty()) + }) + }) + When("repo is github", func() { + BeforeEach(func() { + repo.RepoType = "github" + }) + It("should return github url", func() { + url := repo.BuildURL() + Expect(url).Should(Equal(fmt.Sprintf("https://github.com/%s/%s", repo.Org, repo.Repo))) + }) + }) + When("repo is gitlab", func() { + BeforeEach(func() { + repo.RepoType = "gitlab" + repo.BaseURL = "http://test.com" + repo.Org = "" + }) + It("should return gitlab url", func() { + url := repo.BuildURL() + Expect(url).Should(Equal(fmt.Sprintf("%s/%s/%s.git", repo.BaseURL, repo.Owner, repo.Repo))) + }) + }) + }) }) var _ = Describe("NewRepoFromURL func", func() { diff --git a/internal/pkg/plugininstaller/common/state.go b/internal/pkg/plugininstaller/common/state.go deleted file mode 100644 index 621aa74c9..000000000 --- a/internal/pkg/plugininstaller/common/state.go +++ /dev/null @@ -1,42 +0,0 @@ -package common - -import ( - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func GetPluginAllK8sState(nameSpace string, anFilter, labelFilter map[string]string) (statemanager.ResourceState, error) { - // 1. init kube client - kubeClient, err := k8s.NewClient() - if err != nil { - return nil, err - } - - // 2. get all related resource - allResource, err := kubeClient.GetResourceStatus(nameSpace, anFilter, labelFilter) - if err != nil { - log.Debugf("helm status: get status failed: %s", err) - return nil, err - } - - // 3. transfer resources status to workflows - state := &helm.InstanceState{} - for _, dep := range allResource.Deployment { - state.Workflows.AddDeployment(dep.Name, dep.Ready) - } - for _, sts := range allResource.StatefulSet { - state.Workflows.AddStatefulset(sts.Name, sts.Ready) - } - for _, ds := range allResource.DaemonSet { - state.Workflows.AddDaemonset(ds.Name, ds.Ready) - } - - retMap, err := state.ToStringInterfaceMap() - if err != nil { - return nil, err - } - log.Debugf("Return map: %v.", retMap) - return retMap, nil -} diff --git a/internal/pkg/plugininstaller/helm/state.go b/internal/pkg/plugininstaller/helm/state.go index 28d126baf..e4c69a343 100644 --- a/internal/pkg/plugininstaller/helm/state.go +++ b/internal/pkg/plugininstaller/helm/state.go @@ -2,9 +2,10 @@ package helm import ( "github.com/devstream-io/devstream/internal/pkg/plugininstaller" - "github.com/devstream-io/devstream/internal/pkg/plugininstaller/common" "github.com/devstream-io/devstream/internal/pkg/statemanager" "github.com/devstream-io/devstream/pkg/util/helm" + "github.com/devstream-io/devstream/pkg/util/k8s" + "github.com/devstream-io/devstream/pkg/util/log" ) // GetPluginAllState will get the State of k8s Deployment, DaemonSet and StatefulSet resources @@ -14,9 +15,20 @@ func GetPluginAllState(options plugininstaller.RawOptions) (statemanager.Resourc return nil, err } + kubeClient, err := k8s.NewClient() + if err != nil { + log.Debugf("helm init k8s client to get state failed: %+v", err) + return nil, err + } + anFilter := map[string]string{ helm.GetAnnotationName(): opts.GetReleaseName(), } labelFilter := map[string]string{} - return common.GetPluginAllK8sState(opts.GetNamespace(), anFilter, labelFilter) + allResourceState, err := kubeClient.GetResourceStatus(opts.GetNamespace(), anFilter, labelFilter) + if err != nil { + log.Debugf("helm get resource state failed: %+v", err) + return nil, err + } + return allResourceState.ToStringInterfaceMap() } diff --git a/internal/pkg/plugininstaller/jenkins/plugins_test.go b/internal/pkg/plugininstaller/jenkins/plugins_test.go index 9a81400c9..5b92c2d53 100644 --- a/internal/pkg/plugininstaller/jenkins/plugins_test.go +++ b/internal/pkg/plugininstaller/jenkins/plugins_test.go @@ -1,6 +1,8 @@ package jenkins import ( + "errors" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -12,11 +14,24 @@ type mockSuccessPluginsConfig struct{} func (m *mockSuccessPluginsConfig) GetDependentPlugins() (plugins []*jenkins.JenkinsPlugin) { return } -func (m *mockSuccessPluginsConfig) PreConfig(jenkins.JenkinsAPI) (config *jenkins.RepoCascConfig, err error) { - return +func (m *mockSuccessPluginsConfig) PreConfig(jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) { + return &jenkins.RepoCascConfig{ + RepoType: "gitlab", + CredentialID: "test", + }, nil } func (m *mockSuccessPluginsConfig) UpdateJenkinsFileRenderVars(*jenkins.JenkinsFileRenderInfo) {} +type mockErrorPluginsConfig struct{} + +func (m *mockErrorPluginsConfig) GetDependentPlugins() (plugins []*jenkins.JenkinsPlugin) { + return +} +func (m *mockErrorPluginsConfig) PreConfig(jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) { + return nil, errors.New("test_error") +} +func (m *mockErrorPluginsConfig) UpdateJenkinsFileRenderVars(*jenkins.JenkinsFileRenderInfo) {} + var _ = Describe("installPlugins func", func() { var ( mockClient *mockSuccessJenkinsClient @@ -36,14 +51,26 @@ var _ = Describe("preConfigPlugins func", func() { var ( mockClient *mockSuccessJenkinsClient s *mockSuccessPluginsConfig + f *mockErrorPluginsConfig ) - BeforeEach(func() { - mockClient = &mockSuccessJenkinsClient{} - s = &mockSuccessPluginsConfig{} + When("cascConfig is valid", func() { + BeforeEach(func() { + mockClient = &mockSuccessJenkinsClient{} + s = &mockSuccessPluginsConfig{} + }) + It("should work normal", func() { + err := preConfigPlugins(mockClient, []pluginConfigAPI{s}) + Expect(err).Error().ShouldNot(HaveOccurred()) + }) }) - It("should work normal", func() { - err := preConfigPlugins(mockClient, []pluginConfigAPI{s}) - Expect(err).Error().ShouldNot(HaveOccurred()) + When("cascConfig is not valid", func() { + BeforeEach(func() { + mockClient = &mockSuccessJenkinsClient{} + f = &mockErrorPluginsConfig{} + }) + It("should return error", func() { + err := preConfigPlugins(mockClient, []pluginConfigAPI{f}) + Expect(err).Error().Should(HaveOccurred()) + }) }) - }) diff --git a/internal/pkg/plugininstaller/jenkins/validate.go b/internal/pkg/plugininstaller/jenkins/validate.go index 993407abc..cc2ffaf19 100644 --- a/internal/pkg/plugininstaller/jenkins/validate.go +++ b/internal/pkg/plugininstaller/jenkins/validate.go @@ -97,15 +97,13 @@ func ValidateJobConfig(options plugininstaller.RawOptions) (plugininstaller.RawO if os.Getenv(github.TokenEnvKey) == "" { return nil, fmt.Errorf("jenkins-pipeline github should set env %s", github.TokenEnvKey) } - default: - return nil, fmt.Errorf("jenkins-pipeline doesn't support repo type %s", opts.ProjectRepo.RepoType) } // check jenkins job name if strings.Contains(opts.Pipeline.JobName, "/") { strs := strings.Split(opts.Pipeline.JobName, "/") if len(strs) != 2 || len(strs[0]) == 0 || len(strs[1]) == 0 { - return nil, fmt.Errorf("jobName illegal: %s", opts.Pipeline.JobName) + return nil, fmt.Errorf("jenkins jobName illegal: %s", opts.Pipeline.JobName) } } diff --git a/internal/pkg/plugininstaller/jenkins/validate_test.go b/internal/pkg/plugininstaller/jenkins/validate_test.go index 8ebeb9071..7aa934cd5 100644 --- a/internal/pkg/plugininstaller/jenkins/validate_test.go +++ b/internal/pkg/plugininstaller/jenkins/validate_test.go @@ -1,10 +1,14 @@ package jenkins import ( + "fmt" "os" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + "github.com/devstream-io/devstream/pkg/util/scm/github" + "github.com/devstream-io/devstream/pkg/util/scm/gitlab" ) var _ = Describe("SetJobDefaultConfig func", func() { @@ -79,14 +83,42 @@ var _ = Describe("generateRandomSecretToken func", func() { var _ = Describe("ValidateJobConfig func", func() { var ( - jenkinsUser, jenkinsURL, jenkinsFilePath, projectURL string - options map[string]interface{} + githubToken, gitlabToken string + ) + BeforeEach(func() { + githubToken = os.Getenv(github.TokenEnvKey) + gitlabToken = os.Getenv(gitlab.TokenEnvKey) + err := os.Unsetenv(github.TokenEnvKey) + Expect(err).Error().ShouldNot(HaveOccurred()) + err = os.Unsetenv(gitlab.TokenEnvKey) + Expect(err).Error().ShouldNot(HaveOccurred()) + }) + AfterEach(func() { + if githubToken != "" { + os.Setenv(github.TokenEnvKey, githubToken) + } + if gitlabToken != "" { + os.Setenv(gitlab.TokenEnvKey, gitlabToken) + } + }) + var ( + jenkinsUser, jenkinsURL, jenkinsFilePath, projectURL, repoType string + options, projectRepo, pipeline map[string]interface{} ) BeforeEach(func() { jenkinsUser = "test" jenkinsURL = "http://test.jenkins.com/" projectURL = "https://test.gitlab.com/test/test_project" jenkinsFilePath = "http://raw.content.com/Jenkinsfile" + pipeline = map[string]interface{}{ + "jenkinsfilePath": jenkinsFilePath, + "jobName": "test", + } + projectRepo = map[string]interface{}{ + "owner": "test_owner", + "org": "test_org", + "repo": "test_repo", + } options = map[string]interface{}{ "jenkins": map[string]interface{}{ "url": jenkinsURL, @@ -95,9 +127,8 @@ var _ = Describe("ValidateJobConfig func", func() { "scm": map[string]interface{}{ "cloneURL": projectURL, }, - "pipeline": map[string]interface{}{ - "jenkinsfilePath": jenkinsFilePath, - }, + "pipeline": pipeline, + "projectRepo": projectRepo, } }) When("Input field miss", func() { @@ -117,4 +148,56 @@ var _ = Describe("ValidateJobConfig func", func() { Expect(err).Error().Should(HaveOccurred()) }) }) + When("repo type is gitlab and gitlab env is not configured", func() { + BeforeEach(func() { + repoType = "gitlab" + projectRepo["repoType"] = repoType + options["projectRepo"] = projectRepo + }) + It("should return error", func() { + _, err := ValidateJobConfig(options) + Expect(err).Error().Should(HaveOccurred()) + Expect(err.Error()).Should(Equal(fmt.Sprintf("jenkins-pipeline gitlab should set env %s", gitlab.TokenEnvKey))) + }) + }) + When("repo type is github and github env is not configured", func() { + BeforeEach(func() { + repoType = "github" + projectRepo["repoType"] = repoType + options["projectRepo"] = projectRepo + }) + It("should return error", func() { + _, err := ValidateJobConfig(options) + Expect(err).Error().Should(HaveOccurred()) + Expect(err.Error()).Should(Equal(fmt.Sprintf("jenkins-pipeline github should set env %s", github.TokenEnvKey))) + }) + }) + When("jobName is not valid", func() { + BeforeEach(func() { + pipeline["jobName"] = "folder/not_exist/jobName" + options["pipeline"] = pipeline + repoType = "github" + projectRepo["repoType"] = repoType + options["projectRepo"] = projectRepo + os.Setenv(github.TokenEnvKey, "test_env") + }) + It("should return error", func() { + _, err := ValidateJobConfig(options) + Expect(err).Error().Should(HaveOccurred()) + Expect(err.Error()).Should(Equal(fmt.Sprintf("jenkins jobName illegal: %s", pipeline["jobName"]))) + }) + }) + When("all params is right", func() { + BeforeEach(func() { + options["pipeline"] = pipeline + repoType = "github" + projectRepo["repoType"] = repoType + options["projectRepo"] = projectRepo + os.Setenv(github.TokenEnvKey, "test_env") + }) + It("should return nil error", func() { + _, err := ValidateJobConfig(options) + Expect(err).Error().ShouldNot(HaveOccurred()) + }) + }) }) diff --git a/internal/pkg/pluginmanager/downloader.go b/internal/pkg/pluginmanager/downloader.go index a8e9364df..497ff2d31 100644 --- a/internal/pkg/pluginmanager/downloader.go +++ b/internal/pkg/pluginmanager/downloader.go @@ -1,77 +1,33 @@ -// 1. init download -// 2. get assets *.so +// 1. get plugins *.so +// 2. show progress bar on console package pluginmanager import ( "fmt" "net/http" - "os" - "path/filepath" + "time" - "github.com/go-resty/resty/v2" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -const ( - defaultRetryCount = 3 - defaultReleaseUrl = "https://download.devstream.io" + "github.com/devstream-io/devstream/pkg/util/downloader" ) -type plugDownloader func(reqClient *resty.Client, url, plugName string) error - -type DownloadClient struct { - *resty.Client - pluginGetter plugDownloader +type PluginDownloadClient struct { + *http.Client + baseURL string } -func downloadPlugin(reqClient *resty.Client, url, plugName string) error { - response, err := reqClient.R(). - SetOutput(plugName). - SetHeader("Accept", "application/octet-stream"). - Get(url) - if err != nil { - return err - } - if response.StatusCode() != http.StatusOK { - if err = os.Remove(filepath.Join(url, plugName)); err != nil { - return err - } - err = fmt.Errorf("downloading plugin %s from %s status code %d", plugName, url, response.StatusCode()) - log.Error(err) - return err - } - return nil -} - -func NewDownloadClient() *DownloadClient { - dClient := DownloadClient{ - pluginGetter: downloadPlugin, - } - dClient.Client = resty.New() - dClient.SetRetryCount(defaultRetryCount) +func NewPluginDownloadClient(baseURL string) *PluginDownloadClient { + dClient := PluginDownloadClient{} + dClient.Client = http.DefaultClient + dClient.Client.Timeout = time.Second * 60 * 60 + dClient.baseURL = baseURL return &dClient } -func (dc *DownloadClient) download(pluginDir, pluginFilename, version string) error { - dc.SetOutputDirectory(pluginDir) - - // download plug file - downloadURL := fmt.Sprintf("%s/v%s/%s", defaultReleaseUrl, version, pluginFilename) - tmpName := pluginFilename + ".tmp" - err := dc.pluginGetter(dc.Client, downloadURL, tmpName) - if err != nil { - return err - } - // rename, tmp file to real file - err = os.Rename( - filepath.Join(pluginDir, tmpName), - filepath.Join(pluginDir, pluginFilename)) - if err != nil { - log.Error(err) - return err - } - - return nil +// download from release assets +func (pd *PluginDownloadClient) download(pluginsDir, pluginFilename, version string) error { + downloadURL := fmt.Sprintf("%s/v%s/%s", pd.baseURL, version, pluginFilename) + dc := downloader.New().WithProgressBar().WithClient(pd.Client) + _, err := dc.Download(downloadURL, pluginFilename, pluginsDir) + return err } diff --git a/internal/pkg/pluginmanager/downloader_test.go b/internal/pkg/pluginmanager/downloader_test.go index 783631720..1dcaf8fb6 100644 --- a/internal/pkg/pluginmanager/downloader_test.go +++ b/internal/pkg/pluginmanager/downloader_test.go @@ -2,80 +2,77 @@ package pluginmanager import ( "fmt" + "net/http" "os" "path/filepath" - "github.com/go-resty/resty/v2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + "github.com/onsi/gomega/ghttp" ) -var _ = Describe("DownloadClient", Ordered, func() { - // mock download success func - mockPlugSuccessGetter := func(reqClient *resty.Client, url, plugName string) error { - return nil - } - // mock download failed func - mockPlugNotFoundGetter := func(reqClient *resty.Client, url, plugName string) error { - return fmt.Errorf("downloading plugin %s from %s status code %d", plugName, url, 404) - } +var _ = Describe("downloader", func() { var ( - tempDir string - ) - - const ( - validPlugName = "argocdapp_0.0.1-rc1.so" - notExistPlugName = "argocdapp_not_exist.so" - version = "0.0.1-ut-do-not-delete" + server *ghttp.Server + downlodClient *PluginDownloadClient + reqPath, pluginName, pluginVersion, tempDir string ) - BeforeAll(func() { + BeforeEach(func() { tempDir = GinkgoT().TempDir() + server = ghttp.NewServer() + pluginName = "test_plugin" + pluginVersion = "1.0" + reqPath = fmt.Sprintf("/v%s/%s", pluginVersion, pluginName) + downlodClient = NewPluginDownloadClient(server.URL()) }) - Describe("download method failed", func() { - var testTable = []struct { - downloadFunc func(reqClient *resty.Client, url, plugName string) error - plugName string - expectedErrorMsg string - describeMsg string - }{ - { - downloadFunc: mockPlugSuccessGetter, plugName: notExistPlugName, expectedErrorMsg: "no such file or directory", - describeMsg: "should return file not exist if plugin not normal download", - }, - { - downloadFunc: mockPlugNotFoundGetter, plugName: validPlugName, expectedErrorMsg: "404", - describeMsg: "should return 404 if plugin not exist", - }, - } + Describe("download func", func() { + When("server return err code", func() { + BeforeEach(func() { + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", reqPath), + ghttp.RespondWith(http.StatusNotFound, "test"), + ), + ) + }) - for _, testcase := range testTable { - It(testcase.describeMsg, func() { - c := NewDownloadClient() - c.pluginGetter = testcase.downloadFunc - err := c.download(tempDir, testcase.plugName, version) + It("should return err for download from url error", func() { + err := downlodClient.download(tempDir, pluginName, pluginVersion) Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring(testcase.expectedErrorMsg)) + Expect(err.Error()).Should(ContainSubstring("404")) }) - } - }) + }) - Describe("download method success", func() { - It("should reanme file if download success", func() { - tmpFilePath := filepath.Join(tempDir, fmt.Sprintf("%s.tmp", validPlugName)) - f, err := os.Create(tmpFilePath) - defer os.Remove(tmpFilePath) - defer f.Close() - Expect(err).NotTo(HaveOccurred()) - c := NewDownloadClient() - c.pluginGetter = mockPlugSuccessGetter - err = c.download(tempDir, validPlugName, version) - Expect(err).ShouldNot(HaveOccurred()) - renamedFilePath := filepath.Join(tempDir, validPlugName) - _, err = os.Stat(renamedFilePath) - Expect(err).ShouldNot(HaveOccurred()) + When("response return success", func() { + var testContent string + + BeforeEach(func() { + testContent = "test" + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", reqPath), + ghttp.RespondWith(http.StatusOK, testContent), + ), + ) + }) + + It("should return err if body error", func() { + err := downlodClient.download(tempDir, pluginName, pluginVersion) + Expect(err).Error().ShouldNot(HaveOccurred()) + downloadedFile := filepath.Join(tempDir, pluginName) + // check plugin file is downloaded + fileContent, err := os.ReadFile(downloadedFile) + Expect(err).Error().ShouldNot(HaveOccurred()) + Expect(string(fileContent)).Should(Equal(testContent)) + }) }) }) + + AfterEach(func() { + server.Close() + }) }) diff --git a/internal/pkg/pluginmanager/manager.go b/internal/pkg/pluginmanager/manager.go index d39f03a83..e0056536b 100644 --- a/internal/pkg/pluginmanager/manager.go +++ b/internal/pkg/pluginmanager/manager.go @@ -14,6 +14,8 @@ import ( "github.com/devstream-io/devstream/pkg/util/md5" ) +const defaultReleaseUrl = "https://download.devstream.io" + func DownloadPlugins(tools []configmanager.Tool, pluginDir, osName, arch string) error { if pluginDir == "" { return fmt.Errorf(`plugins directory should not be ""`) @@ -22,7 +24,7 @@ func DownloadPlugins(tools []configmanager.Tool, pluginDir, osName, arch string) log.Infof("Using dir <%s> to store plugins.", pluginDir) // download all plugins that don't exist locally - dc := NewPbDownloadClient(defaultReleaseUrl) + dc := NewPluginDownloadClient(defaultReleaseUrl) for _, tool := range tools { pluginName := configmanager.GetPluginNameWithOSAndArch(&tool, osName, arch) @@ -127,7 +129,7 @@ func pluginAndMD5Matches(pluginDir, soFileName, md5FileName, tooName string) err } // redownloadPlugins re-download from remote -func redownloadPlugins(dc *PbDownloadClient, pluginDir, pluginFileName, pluginMD5FileName, version string) error { +func redownloadPlugins(dc *PluginDownloadClient, pluginDir, pluginFileName, pluginMD5FileName, version string) error { if err := os.Remove(filepath.Join(pluginDir, pluginFileName)); err != nil { return err } diff --git a/internal/pkg/pluginmanager/manager_test.go b/internal/pkg/pluginmanager/manager_test.go index 8a56e195c..66e4dec4b 100644 --- a/internal/pkg/pluginmanager/manager_test.go +++ b/internal/pkg/pluginmanager/manager_test.go @@ -102,12 +102,12 @@ var _ = Describe("MD5", func() { Describe("redownloadPlugins func", func() { var ( - pbDownloadClient *PbDownloadClient + pbDownloadClient *PluginDownloadClient pluginVersion string ) BeforeEach(func() { - pbDownloadClient = NewPbDownloadClient("not_exist_url") + pbDownloadClient = NewPluginDownloadClient("not_exist_url") }) When("pluginFile not exist", func() { @@ -135,7 +135,7 @@ var _ = Describe("MD5", func() { reqPath := fmt.Sprintf("/v%s/%s", pluginVersion, file) md5Path := fmt.Sprintf("/v%s/%s", pluginVersion, fileMD5) server = ghttp.NewServer() - pbDownloadClient = NewPbDownloadClient(server.URL()) + pbDownloadClient = NewPluginDownloadClient(server.URL()) rspContent = "reDownload Content" server.AppendHandlers( ghttp.CombineHandlers( diff --git a/internal/pkg/pluginmanager/pbdownloader.go b/internal/pkg/pluginmanager/pbdownloader.go deleted file mode 100644 index 22e207a36..000000000 --- a/internal/pkg/pluginmanager/pbdownloader.go +++ /dev/null @@ -1,91 +0,0 @@ -// 1. get plugins *.so -// 2. show progress bar on console - -package pluginmanager - -import ( - "fmt" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/devstream-io/devstream/pkg/util/downloader" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -type PbDownloadClient struct { - *http.Client - baseURL string -} - -func NewPbDownloadClient(baseURL string) *PbDownloadClient { - dClient := PbDownloadClient{} - dClient.Client = http.DefaultClient - dClient.Client.Timeout = time.Second * 60 * 60 - dClient.baseURL = baseURL - return &dClient -} - -// download from release assets -func (pd *PbDownloadClient) download(pluginsDir, pluginFilename, version string) error { - - err := createPathIfNotExists(pluginsDir) - if err != nil { - return err - } - - downloadURL := fmt.Sprintf("%s/v%s/%s", pd.baseURL, version, pluginFilename) - log.Debugf("Downloading url is: %s.", downloadURL) - - tmpName := pluginFilename + ".tmp" - - resp, err := pd.Get(downloadURL) - if err != nil { - return err - } - pluginTmpLocation := filepath.Join(pluginsDir, tmpName) - pluginLocation := filepath.Join(pluginsDir, pluginFilename) - - if resp.StatusCode == http.StatusOK { - log.Infof("Downloading: [%s] ...", pluginFilename) - - downFile, err := os.Create(pluginTmpLocation) - if err != nil { - return err - } - - defer downFile.Close() - // create progress bar when reading response body - _, errSetup := downloader.SetUpProgressBar(resp, downFile) - if errSetup != nil { - log.Error(errSetup) - return errSetup - } - } else { - log.Errorf("[%s] download failed, %s.", pluginFilename, resp.Status) - if err = os.Remove(pluginTmpLocation); err != nil { - log.Errorf("Remove [%s] failed, %s.", pluginLocation, err) - } - return fmt.Errorf("downloading %s from %s status code %d", pluginFilename, downloadURL, resp.StatusCode) - } - - // rename, tmp file to real file - if err = os.Rename(pluginTmpLocation, pluginLocation); err != nil { - log.Error(err) - return err - } - return nil -} - -func createPathIfNotExists(path string) error { - _, err := os.Stat(path) - if !os.IsNotExist(err) { - return err - } - if err := os.MkdirAll(path, os.ModePerm); err != nil { - return err - } - return nil -} diff --git a/internal/pkg/pluginmanager/pbdownloader_test.go b/internal/pkg/pluginmanager/pbdownloader_test.go deleted file mode 100644 index 11835905e..000000000 --- a/internal/pkg/pluginmanager/pbdownloader_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package pluginmanager - -import ( - "fmt" - "net/http" - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/onsi/gomega/ghttp" -) - -var _ = Describe("pbdownloader", func() { - var ( - server *ghttp.Server - pbDownlodClient *PbDownloadClient - reqPath, pluginName, pluginVersion, tempDir string - ) - - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - server = ghttp.NewServer() - pluginName = "test_plugin" - pluginVersion = "1.0" - reqPath = fmt.Sprintf("/v%s/%s", pluginVersion, pluginName) - pbDownlodClient = NewPbDownloadClient(server.URL()) - - }) - - Describe("download func", func() { - When("server return err code", func() { - BeforeEach(func() { - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWith(http.StatusNotFound, "test"), - ), - ) - }) - - It("should return err for download from url error", func() { - err := pbDownlodClient.download(tempDir, pluginName, pluginVersion) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("404")) - }) - }) - - When("response return success", func() { - var testContent string - - BeforeEach(func() { - testContent = "test" - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWith(http.StatusOK, testContent), - ), - ) - }) - - It("should return err if body error", func() { - err := pbDownlodClient.download(tempDir, pluginName, pluginVersion) - Expect(err).Error().ShouldNot(HaveOccurred()) - downloadedFile := filepath.Join(tempDir, pluginName) - // check plugin file is downloaded - fileContent, err := os.ReadFile(downloadedFile) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(string(fileContent)).Should(Equal(testContent)) - }) - }) - }) - - AfterEach(func() { - server.Close() - }) -}) diff --git a/internal/pkg/show/config/embed_gen.go b/internal/pkg/show/config/embed_gen.go index 58344def3..9ccca63ab 100644 --- a/internal/pkg/show/config/embed_gen.go +++ b/internal/pkg/show/config/embed_gen.go @@ -21,6 +21,9 @@ var ( //go:embed plugins/ci-generic.yaml CiGenericDefaultConfig string + //go:embed plugins/devlake-config.yaml + DevlakeConfigDefaultConfig string + //go:embed plugins/devlake.yaml DevlakeDefaultConfig string @@ -96,6 +99,7 @@ var pluginDefaultConfigs = map[string]string{ "argocdapp": ArgocdappDefaultConfig, "artifactory": ArtifactoryDefaultConfig, "ci-generic": CiGenericDefaultConfig, + "devlake-config": DevlakeConfigDefaultConfig, "devlake": DevlakeDefaultConfig, "githubactions-golang": GithubactionsGolangDefaultConfig, "githubactions-nodejs": GithubactionsNodejsDefaultConfig, diff --git a/internal/pkg/show/config/plugins/devlake-config.yaml b/internal/pkg/show/config/plugins/devlake-config.yaml new file mode 100644 index 000000000..c5ea90e8e --- /dev/null +++ b/internal/pkg/show/config/plugins/devlake-config.yaml @@ -0,0 +1,28 @@ +tools: +# name of the tool +- name: devlake-config + # id of the tool instance + instanceID: default + # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. + dependsOn: [ ] + # options for the plugin + options: + devlakeAddr: http://127.0.0.1:1234 + plugins: + - name: github + connections: + - name: "default" + endpoint: "https://github.com/changeme/changeme" + proxy: "" + rateLimitPerHour: 0 + auth: + token: "xxx" + - name: jira + connections: + - name: "default" + endpoint: "https://changeme.atlassian.net" + proxy: "" + rateLimitPerHour: 0 + auth: + username: "changeme" + password: "changeme" diff --git a/internal/pkg/show/config/plugins/jenkins-pipeline.yaml b/internal/pkg/show/config/plugins/jenkins-pipeline.yaml index f8f3073d2..df5551dcc 100644 --- a/internal/pkg/show/config/plugins/jenkins-pipeline.yaml +++ b/internal/pkg/show/config/plugins/jenkins-pipeline.yaml @@ -18,16 +18,16 @@ tools: enableRestart: true scm: # cloneURL is the project repo location, this can be http address or git address - cloneURL: git@gitlab.com/root/test-project.git + cloneURL: git@gitlab.com/root/test-exmaple.git # apiURL is the api address of gitlab, if you use github, this field can be empty - apiURL: http://gitlab.com - # project branch, master as default - branch: master + apiURL: http://gitlab.example.com + # project branch, if your repo type is github, default branch will be main. if your repo type is gitlab, default branch will be master + branch: YOUR_PROJECT_BRANCH pipeline: # jobName is jenkins's job name; or ; e.g. jobs/test-job, test-job, jobs2/test-job jobName: test-job # jenkinsfilePath is the location describe how to get Jenkinsfile, it can be remote or local - jenkinsfilePath: https://raw.githubusercontent.com/dtm-jenkins-pipeline-example/main/springboot/Jenkinsfile + jenkinsfilePath: https://raw.githubusercontent.com/devstream-io/devstream/main/staging/dtm-jenkins-pipeline-example/general/Jenkinsfile imageRepo: # image repo URL for pulling/pushing url: http://harbor.example.com:80 @@ -35,17 +35,17 @@ tools: user: admin dingTalk: # dingtalk robot name - name: dingtalk + name: YOUR_DINGTALK_ROBOT_NAME # dingtalk webhook - webhook: https://oapi.dingtalk.com/robot/send?access_token=test - # dingtalk securityType - securityType: "SECRET" + webhook: https://oapi.dingtalk.com/robot/send?access_token=changemeByConfig + # dingtalk securityType, we support "SECRET" and "KEY" + securityType: YOUR_DINGTALK_SECRET_TYPE # dingtalk securityValue - securityValue: SECRET_VALUE + securityValue: YOUR_DINGTALK_SECRET_VALUE sonarqube: # sonarqube address - url: http://sonar.com + url: http://sonar.example.com # sonarqube token - token: squ_test - # sonarqube name + token: YOUR_SONAR_TOKEN + # sonarqube name in jenkins name: sonar_test diff --git a/internal/pkg/upgrade/upgrade.go b/internal/pkg/upgrade/upgrade.go index 8ac4e80f3..1585af99f 100644 --- a/internal/pkg/upgrade/upgrade.go +++ b/internal/pkg/upgrade/upgrade.go @@ -1,15 +1,14 @@ package upgrade import ( - "fmt" "os" "path/filepath" "runtime" "github.com/Masterminds/semver" - "github.com/tcnksm/go-input" "github.com/devstream-io/devstream/internal/pkg/version" + "github.com/devstream-io/devstream/pkg/util/interact" "github.com/devstream-io/devstream/pkg/util/log" "github.com/devstream-io/devstream/pkg/util/scm/git" "github.com/devstream-io/devstream/pkg/util/scm/github" @@ -69,23 +68,22 @@ func Upgrade(continueDirectly bool) error { // 2. Check whether Upgrade is needed // Use Semantic Version to judge. "https://semver.org/" - ok, err := checkUpgrade(version.Version, ltstReleaseTagName) + shouldUpgrade, err := checkUpgrade(version.Version, ltstReleaseTagName) if err != nil { return err } - if ok { + if shouldUpgrade { log.Infof("Dtm upgrade: new dtm version: %v is available.", ltstReleaseTagName) if !continueDirectly { - userInput := readUserInput() - if userInput == "n" { + continued := interact.AskUserIfContinue("Would you like to Upgrade? [y/n]") + if !continued { os.Exit(0) } } // 3. Download the latest release version of dtm log.Info("Dtm upgrade: downloading the latest release version of dtm, please wait for a while.") - // TODO(hxcGit): add download progress bar if err = ghClient.DownloadAsset(ltstReleaseTagName, assetName, dtmTmpFileName); err != nil { log.Debugf("Failed to download dtm: %v-%v.", ltstReleaseTagName, assetName) return err @@ -165,28 +163,3 @@ func applyUpgrade(workDir string) error { return nil } - -// TODO(hxcGit): reuse pluginengine.readUserInput() -func readUserInput() string { - ui := &input.UI{ - Writer: os.Stdout, - Reader: os.Stdin, - } - - query := "Would you like to Upgrade? [y/n]" - userInput, err := ui.Ask(query, &input.Options{ - Required: true, - Default: "n", - Loop: true, - ValidateFunc: func(s string) error { - if s != "y" && s != "n" { - return fmt.Errorf("input must be y or n") - } - return nil - }, - }) - if err != nil { - log.Fatal(err) - } - return userInput -} diff --git a/mkdocs.yml b/mkdocs.yml index 2933ce611..05c4ae553 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -89,6 +89,7 @@ nav: - Overview: . - understanding_the_basics*.md - quickstart*.md + - install*.md - Core Concepts: - core-concepts/core-concepts*.md - core-concepts/config*.md @@ -97,7 +98,6 @@ nav: - core-concepts/dependencies*.md - core-concepts/output*.md - Commands: - - commands/install*.md - commands/autocomplete*.md - commands/*.md - Plugins: diff --git a/pkg/util/cli/viper.go b/pkg/util/cli/viper.go new file mode 100644 index 000000000..cbf21ddab --- /dev/null +++ b/pkg/util/cli/viper.go @@ -0,0 +1,23 @@ +package cli + +import ( + "log" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type PreRunFunc func(cmd *cobra.Command, args []string) + +// BindPFlags accepts a list of flag names and binds the corresponding flags to Viper. This function servers +// as a workaround to a known bug in Viper, in which two commands can't share the same flag name. +// To learn more about the bug, see: https://github.com/spf13/viper/issues/233 +func BindPFlags(flagNames []string) PreRunFunc { + return func(cmd *cobra.Command, args []string) { + for _, name := range flagNames { + if err := viper.BindPFlag(name, cmd.Flags().Lookup(name)); err != nil { + log.Fatalf("Failed to bind flag %s: %s", name, err) + } + } + } +} diff --git a/pkg/util/downloader/downloader.go b/pkg/util/downloader/downloader.go index f3ed3a3e9..52d289eff 100644 --- a/pkg/util/downloader/downloader.go +++ b/pkg/util/downloader/downloader.go @@ -14,57 +14,131 @@ import ( "github.com/devstream-io/devstream/pkg/util/log" ) -// if filename is "", use the remote filename at local. -func Download(url, filename, targetDir string) (int64, error) { - log.Debugf("Target dir: %s.", targetDir) - if url == "" { - return 0, fmt.Errorf("url must not be empty: %s", url) - } - if filename == "" { - // when url is empty filepath.Base(url) will return "." - filename = filepath.Base(url) +type Downloader struct { + EnableProgressBar bool + client *http.Client +} + +func New() *Downloader { + return &Downloader{ + client: &http.Client{}, } - if filename == "." { - return 0, fmt.Errorf("failed to get the filename from url: %s", url) +} + +func (d *Downloader) WithProgressBar() *Downloader { + d.EnableProgressBar = true + return d +} + +func (d *Downloader) WithClient(client *http.Client) *Downloader { + d.client = client + return d +} + +// Download a file from the URL to the target path +// if filename is "", use the remote filename at local. +func (d *Downloader) Download(url, filename, targetDir string) (size int64, err error) { + // handle filename and target dir + filename, err = parseFilenameAndCreateTargetDir(url, filename, targetDir) + if err != nil { + return 0, err } - if err := os.MkdirAll(targetDir, 0755); err != nil { + // get http response + resp, err := d.getHttpResponse(url) + if err != nil { return 0, err } + defer func(body io.ReadCloser) { + err := body.Close() + if err != nil { + log.Errorf("Close response body failed: %s", err) + } + }(resp.Body) - f, err := os.Create(filepath.Join(targetDir, filename)) + pluginTmpLocation := filepath.Join(targetDir, filename+".tmp") + pluginLocation := filepath.Join(targetDir, filename) + + // download to tmp file + log.Infof("Downloading: [%s] ...", filename) + downFile, err := os.Create(pluginTmpLocation) if err != nil { return 0, err } defer func() { - err := f.Close() + err := downFile.Close() + if err != nil { + log.Debugf("download create file failed: %s", err) + } + err = removeFileIfExists(pluginTmpLocation) if err != nil { log.Debugf("download create file failed: %s", err) } }() - return downloadProgress(url, f) + + if d.EnableProgressBar { + // create progress bar when reading response body + size, err = SetUpProgressBar(resp, downFile) + } else { + // just copy response body to file + size, err = io.Copy(downFile, resp.Body) + } + + if err != nil { + return 0, err + } + + // rename, tmp file to real file + if err = os.Rename(pluginTmpLocation, pluginLocation); err != nil { + return 0, err + } + return size, nil } -func FetchContentFromURL(url string) ([]byte, error) { - resp, err := http.Get(url) +func parseFilenameAndCreateTargetDir(url, filename, targetDir string) (finalFilename string, err error) { + log.Debugf("Downloading url is: %s.", url) + log.Debugf("Target dir: %s.", targetDir) + if url == "" { + return "", fmt.Errorf("url must not be empty: %s", url) + } + // get filename from local or remote + if filename != "" { + // use local filename + // when filename is "/", ".", "..", the filepath.Base will return "/", ".", ".." + finalFilename = filepath.Base(filename) + if finalFilename == "/" || finalFilename == "." || finalFilename == ".." { + return "", fmt.Errorf("filename must not be dir") + } + } else { + // use remote filename + // when url is empty filepath.Base(url) will return "." + finalFilename = filepath.Base(url) + if finalFilename == "." { + return "", fmt.Errorf("failed to get the filename from url: %s", url) + } + } + + if err := os.MkdirAll(targetDir, 0755); err != nil { + return "", err + } + + return finalFilename, nil +} + +func (d *Downloader) getHttpResponse(url string) (*http.Response, error) { + resp, err := d.client.Get(url) // check response error if err != nil { log.Debugf("Download from url failed: %s", err) return nil, err } - defer func(body io.ReadCloser) { - err := body.Close() - if err != nil { - log.Errorf("Close response body failed: %s", err) - } - }(resp.Body) if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("Download file from url failed: %+v", resp) } - return io.ReadAll(resp.Body) + return resp, nil } // setUpProgressBar create bar and setup @@ -86,14 +160,13 @@ func SetUpProgressBar(resp *http.Response, downFile io.Writer) (int64, error) { return io.Copy(writer, source) } -// download from the URL to the dst and watch the progress -func downloadProgress(url string, dst io.Writer) (int64, error) { +func FetchContentFromURL(url string) ([]byte, error) { resp, err := http.Get(url) // check response error if err != nil { log.Debugf("Download from url failed: %s", err) - return 0, err + return nil, err } defer func(body io.ReadCloser) { err := body.Close() @@ -103,7 +176,15 @@ func downloadProgress(url string, dst io.Writer) (int64, error) { }(resp.Body) if resp.StatusCode != http.StatusOK { - return 0, fmt.Errorf("Download file from url failed: %+v", resp) + return nil, fmt.Errorf("Download file from url failed: %+v", resp) + } + + return io.ReadAll(resp.Body) +} + +func removeFileIfExists(filename string) error { + if _, err := os.Stat(filename); os.IsNotExist(err) { + return nil } - return SetUpProgressBar(resp, dst) + return os.Remove(filename) } diff --git a/pkg/util/downloader/downloader_test.go b/pkg/util/downloader/downloader_test.go index 6c30a5835..b5b7fec33 100644 --- a/pkg/util/downloader/downloader_test.go +++ b/pkg/util/downloader/downloader_test.go @@ -38,25 +38,25 @@ var _ = Describe("Downloader", func() { Context("Downloader test", func() { When("input params is wrong", func() { It("returns an error when url is empty", func() { - size, err := downloader.Download("", ".", tempDir) + size, err := downloader.New().Download("", ".", tempDir) Expect(err).To(HaveOccurred()) Expect(size).To(Equal(int64(0))) }) It("returns an error when filename is [.]", func() { - size, err := downloader.Download(url, ".", tempDir) + size, err := downloader.New().Download(url, ".", tempDir) Expect(err).To(HaveOccurred()) Expect(size).To(Equal(int64(0))) }) It("returns an error when filename is dir", func() { - size, err := downloader.Download(url, "/", tempDir) + size, err := downloader.New().Download(url, "/", tempDir) Expect(err).To(HaveOccurred()) Expect(size).To(Equal(int64(0))) }) It("returns an error when the targetDir is empty", func() { - size, err := downloader.Download(url, "download.txt", "") + size, err := downloader.New().Download(url, "download.txt", "") Expect(err).To(HaveOccurred()) Expect(size).To(Equal(int64(0))) }) @@ -71,7 +71,7 @@ var _ = Describe("Downloader", func() { }) It("returns an error when the url is not right", func() { errorURL := path.Join(s.URL(), failReqPath) - size, err := downloader.Download(errorURL, "download.txt", tempDir) + size, err := downloader.New().Download(errorURL, "download.txt", tempDir) Expect(err).To(HaveOccurred()) Expect(size).To(Equal(int64(0))) }) @@ -85,7 +85,7 @@ var _ = Describe("Downloader", func() { }) When("filename is empty", func() { It("should returns an error ", func() { - size, err := downloader.Download(url, "", tempDir) + size, err := downloader.New().Download(url, "", tempDir) Expect(err).NotTo(HaveOccurred()) Expect(size).NotTo(Equal(int64(0))) }) @@ -93,7 +93,7 @@ var _ = Describe("Downloader", func() { When("fileName is right", func() { It("should get fileName with content", func() { fileName := "testFile" - size, err := downloader.Download(url, fileName, tempDir) + size, err := downloader.New().Download(url, fileName, tempDir) Expect(err).NotTo(HaveOccurred()) Expect(size).NotTo(Equal(int64(0))) fileContent, err := os.ReadFile(filepath.Join(tempDir, fileName)) diff --git a/pkg/util/file/dir.go b/pkg/util/file/dir.go index 8ebc83e52..ab174c724 100644 --- a/pkg/util/file/dir.go +++ b/pkg/util/file/dir.go @@ -22,7 +22,6 @@ func WalkDir( srcPath string, filterFunc DirFIleFilterFunc, fileNameFunc DirFileNameFunc, processFunc DirFileProcessFunc, ) (map[string][]byte, error) { contentMap := make(map[string][]byte) - // 1. create temp dir for destination if err := filepath.Walk(srcPath, func(path string, info fs.FileInfo, err error) error { if err != nil { log.Debugf("Walk error: %s.", err) @@ -33,7 +32,7 @@ func WalkDir( return nil } - // if file endswith tpl, render this file, else copy this file directly + // if file ends-with tpl, render this file, else copy this file directly dstFileName := fileNameFunc(path, srcPath) content, err := processFunc(path) if err != nil { diff --git a/pkg/util/helm/state.go b/pkg/util/helm/state.go deleted file mode 100644 index 2ea66ac4d..000000000 --- a/pkg/util/helm/state.go +++ /dev/null @@ -1,78 +0,0 @@ -package helm - -import ( - "bytes" - - "gopkg.in/yaml.v3" -) - -type InstanceState struct { - Workflows Workflows -} - -func (is *InstanceState) ToStringInterfaceMap() (map[string]interface{}, error) { - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - defer encoder.Close() - encoder.SetIndent(2) - err := encoder.Encode(&is.Workflows) - if err != nil { - return nil, err - } - wfs := buf.String() - - return map[string]interface{}{ - "workflows": wfs, - }, nil -} - -type Workflows struct { - Deployments []Deployment `yaml:"deployments,omitempty"` - Daemonsets []Daemonset `yaml:"daemonsets,omitempty"` - Statefulsets []Statefulset `yaml:"statefulsets,omitempty"` -} - -func (w *Workflows) AddDeployment(name string, ready bool) { - if w.Deployments == nil { - w.Deployments = make([]Deployment, 0) - } - w.Deployments = append(w.Deployments, Deployment{ - Name: name, - Ready: ready, - }) -} - -func (w *Workflows) AddDaemonset(name string, ready bool) { - if w.Daemonsets == nil { - w.Daemonsets = make([]Daemonset, 0) - } - w.Daemonsets = append(w.Daemonsets, Daemonset{ - Name: name, - Ready: ready, - }) -} - -func (w *Workflows) AddStatefulset(name string, ready bool) { - if w.Statefulsets == nil { - w.Statefulsets = make([]Statefulset, 0) - } - w.Statefulsets = append(w.Statefulsets, Statefulset{ - Name: name, - Ready: ready, - }) -} - -type Deployment struct { - Name string `yaml:"name"` - Ready bool `yaml:"ready"` -} - -type Daemonset struct { - Name string `yaml:"name"` - Ready bool `yaml:"ready"` -} - -type Statefulset struct { - Name string `yaml:"name"` - Ready bool `yaml:"ready"` -} diff --git a/pkg/util/helm/state_test.go b/pkg/util/helm/state_test.go deleted file mode 100644 index 117f4937b..000000000 --- a/pkg/util/helm/state_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package helm - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/require" - - "gopkg.in/yaml.v3" -) - -func TestInstanceState_ToStringInterfaceMap(t *testing.T) { - wf := Workflows{ - Deployments: []Deployment{{"Deployment", true}, {"Deployment2", false}}, - Daemonsets: []Daemonset{{"Daemonset", true}}, - Statefulsets: []Statefulset{{"Statefulset", true}}, - } - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - defer encoder.Close() - encoder.SetIndent(2) - err := encoder.Encode(&wf) - require.NoError(t, err) - - wfs := buf.String() - - tests := []struct { - name string - Workflows Workflows - want map[string]interface{} - }{ - // TODO: Add test cases. - {"base", wf, map[string]interface{}{"workflows": wfs}}, - // {"base encode error", nil, map[string]interface{}{"workflows": struct{}{}}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - is := &InstanceState{ - Workflows: tt.Workflows, - } - got, err := is.ToStringInterfaceMap() - require.NoErrorf(t, err, "InstanceState.ToStringInterfaceMap() error = %v", err) - require.Equalf(t, got, tt.want, "InstanceState.ToStringInterfaceMap() = %v, want %v", got, tt.want) - }) - } -} - -func TestWorkflows_AddDeployment(t *testing.T) { - one, two := Deployment{"Deployment", true}, Deployment{"Deployment2", false} - want, want2 := []Deployment{one}, []Deployment{one, two} - wf := Workflows{ - Deployments: []Deployment{}, - Daemonsets: []Daemonset{}, - Statefulsets: []Statefulset{}, - } - wf2 := Workflows{ - Deployments: []Deployment{one}, - Daemonsets: []Daemonset{}, - Statefulsets: []Statefulset{}, - } - var wf3 Workflows - tests := []struct { - name string - wfs Workflows - element Deployment - want []Deployment - }{ - // TODO: Add test cases. - {"base empty", wf, one, want}, - {"base not empty", wf2, two, want2}, - {"base nil", wf3, one, want}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.wfs.AddDeployment(tt.element.Name, tt.element.Ready) - got, want := tt.wfs.Deployments, tt.want - require.Equalf(t, got, want, "Workflows.AddDeployment() = %v, want %v", got, tt.want) - }) - } -} - -func TestWorkflows_AddDaemonset(t *testing.T) { - one, two := Daemonset{"Daemonset", true}, Daemonset{"Daemonset2", false} - want, want2 := []Daemonset{one}, []Daemonset{one, two} - wf := Workflows{ - Deployments: []Deployment{}, - Daemonsets: []Daemonset{}, - Statefulsets: []Statefulset{}, - } - wf2 := Workflows{ - Deployments: []Deployment{}, - Daemonsets: []Daemonset{one}, - Statefulsets: []Statefulset{}, - } - var wf3 Workflows - tests := []struct { - name string - wfs Workflows - element Daemonset - want []Daemonset - }{ - // TODO: Add test cases. - {"base empty", wf, one, want}, - {"base not empty", wf2, two, want2}, - {"base nil", wf3, one, want}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.wfs.AddDaemonset(tt.element.Name, tt.element.Ready) - got, want := tt.wfs.Daemonsets, tt.want - require.Equalf(t, got, want, "Workflows.AddDaemonSet() = %v, want %v", got, tt.want) - }) - } -} - -func TestWorkflows_AddStatefulset(t *testing.T) { - one, two := Statefulset{"Statefulset", true}, Statefulset{"Statefulset", false} - want, want2 := []Statefulset{one}, []Statefulset{one, two} - wf := Workflows{ - Deployments: []Deployment{}, - Daemonsets: []Daemonset{}, - Statefulsets: []Statefulset{}, - } - wf2 := Workflows{ - Deployments: []Deployment{}, - Daemonsets: []Daemonset{}, - Statefulsets: []Statefulset{one}, - } - var wf3 Workflows - tests := []struct { - name string - wfs Workflows - element Statefulset - want []Statefulset - }{ - // TODO: Add test cases. - {"base empty", wf, one, want}, - {"base not empty", wf2, two, want2}, - {"base nil", wf3, one, want}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.wfs.AddStatefulset(tt.element.Name, tt.element.Ready) - got, want := tt.wfs.Statefulsets, tt.want - require.Equalf(t, got, want, "Workflows.AddDaemonSet() = %v, want %v", got, tt.want) - }) - } -} diff --git a/internal/pkg/pluginengine/helper.go b/pkg/util/interact/readUserInput.go similarity index 71% rename from internal/pkg/pluginengine/helper.go rename to pkg/util/interact/readUserInput.go index 1c0578958..f1c137559 100644 --- a/internal/pkg/pluginengine/helper.go +++ b/pkg/util/interact/readUserInput.go @@ -1,4 +1,4 @@ -package pluginengine +package interact import ( "fmt" @@ -9,13 +9,14 @@ import ( "github.com/devstream-io/devstream/pkg/util/log" ) -func readUserInput() string { +// AskUserIfContinue asks the user if he wants to continue +// default is false +func AskUserIfContinue(query string) (continued bool) { ui := &input.UI{ Writer: os.Stdout, Reader: os.Stdin, } - query := "Continue? [y/n]" userInput, err := ui.Ask(query, &input.Options{ Required: true, Default: "n", @@ -30,5 +31,5 @@ func readUserInput() string { if err != nil { log.Fatal(err) } - return userInput + return userInput == "y" } diff --git a/pkg/util/k8s/state.go b/pkg/util/k8s/state.go index 32a6e1b43..5214142b3 100644 --- a/pkg/util/k8s/state.go +++ b/pkg/util/k8s/state.go @@ -1,6 +1,10 @@ package k8s import ( + "bytes" + + "gopkg.in/yaml.v3" + "github.com/devstream-io/devstream/pkg/util/log" ) @@ -88,3 +92,19 @@ func filterByAnnotation(anInfo map[string]string, anFilter map[string]string) bo } return true } + +func (s *AllResourceStatus) ToStringInterfaceMap() (map[string]interface{}, error) { + var buf bytes.Buffer + encoder := yaml.NewEncoder(&buf) + defer encoder.Close() + encoder.SetIndent(2) + err := encoder.Encode(s) + if err != nil { + return nil, err + } + wfs := buf.String() + + return map[string]interface{}{ + "workflows": wfs, + }, nil +} diff --git a/pkg/util/scm/client.go b/pkg/util/scm/client.go index bfc19089a..dfff68242 100644 --- a/pkg/util/scm/client.go +++ b/pkg/util/scm/client.go @@ -28,9 +28,9 @@ func NewClient(repoInfo *git.RepoInfo) (ClientOperation, error) { type ClientOperation interface { InitRepo() error DeleteRepo() error - PushLocalFileToRepo(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) - GetLocationInfo(path string) ([]*git.RepoFileStatus, error) + PushFiles(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) DeleteFiles(commitInfo *git.CommitInfo) error + GetPathInfo(path string) ([]*git.RepoFileStatus, error) AddWebhook(webhookConfig *git.WebhookConfig) error DeleteWebhook(webhookConfig *git.WebhookConfig) error } @@ -41,8 +41,11 @@ func PushInitRepo(client ClientOperation, commitInfo *git.CommitInfo) error { return err } - // if encounter rollout error, delete repo - var needRollBack bool + var ( + // if encounter rollout error, delete repo + needRollBack bool + err error + ) defer func() { if !needRollBack { return @@ -54,7 +57,7 @@ func PushInitRepo(client ClientOperation, commitInfo *git.CommitInfo) error { }() // 2. push local path to repo - needRollBack, err := client.PushLocalFileToRepo(commitInfo, false) + needRollBack, err = client.PushFiles(commitInfo, false) return err } diff --git a/pkg/util/scm/client_test.go b/pkg/util/scm/client_test.go index 0dc42e850..1b775b9ea 100644 --- a/pkg/util/scm/client_test.go +++ b/pkg/util/scm/client_test.go @@ -23,7 +23,7 @@ func (m *mockRepoStruct) InitRepo() error { } return nil } -func (m *mockRepoStruct) PushLocalFileToRepo(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) { +func (m *mockRepoStruct) PushFiles(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) { if m.pushRaiseError { return m.needRollBack, errors.New("push error") } @@ -33,7 +33,7 @@ func (m *mockRepoStruct) DeleteRepo() error { m.deleteFuncIsRun = true return nil } -func (m *mockRepoStruct) GetLocationInfo(path string) ([]*git.RepoFileStatus, error) { +func (m *mockRepoStruct) GetPathInfo(path string) ([]*git.RepoFileStatus, error) { return nil, nil } func (m *mockRepoStruct) DeleteFiles(commitInfo *git.CommitInfo) error { diff --git a/pkg/util/scm/git/commit.go b/pkg/util/scm/git/commit.go index eddb10be0..739f62ac1 100644 --- a/pkg/util/scm/git/commit.go +++ b/pkg/util/scm/git/commit.go @@ -16,12 +16,13 @@ type CommitInfo struct { GitFileMap GitFileContentMap } -// GitFileInfo contains file local path and remote git path +// GitFilePathInfo contains file local path and remote git path type GitFilePathInfo struct { - SourcePath string - DestionationPath string + SourcePath string + DestinationPath string } +// unused func GetFileContent(files []*GitFilePathInfo) GitFileContentMap { gitFileMap := make(map[string][]byte) for _, f := range files { @@ -30,15 +31,16 @@ func GetFileContent(files []*GitFilePathInfo) GitFileContentMap { log.Warnf("Repo Process file content error: %s", err) continue } - if f.DestionationPath == "" { + if f.DestinationPath == "" { log.Warnf("Repo file destination path is not set") continue } - gitFileMap[f.DestionationPath] = content + gitFileMap[f.DestinationPath] = content } return gitFileMap } +// unused func GenerateGitFileInfo(filePaths []string, gitDirPath string) ([]*GitFilePathInfo, error) { gitFileInfos := make([]*GitFilePathInfo, 0) for _, filePath := range filePaths { @@ -48,8 +50,8 @@ func GenerateGitFileInfo(filePaths []string, gitDirPath string) ([]*GitFilePathI } if !info.IsDir() { gitFileInfos = append(gitFileInfos, &GitFilePathInfo{ - SourcePath: filePath, - DestionationPath: filepath.Join(gitDirPath, filePath), + SourcePath: filePath, + DestinationPath: filepath.Join(gitDirPath, filePath), }) continue } @@ -70,8 +72,8 @@ func GenerateGitFileInfo(filePaths []string, gitDirPath string) ([]*GitFilePathI repoPath = filepath.Join(gitDirPath, repoPath) } gitFileInfos = append(gitFileInfos, &GitFilePathInfo{ - SourcePath: path, - DestionationPath: repoPath, + SourcePath: path, + DestinationPath: repoPath, }) return nil }) diff --git a/pkg/util/scm/git/commit_test.go b/pkg/util/scm/git/commit_test.go index 571202d40..86937cdf2 100644 --- a/pkg/util/scm/git/commit_test.go +++ b/pkg/util/scm/git/commit_test.go @@ -22,8 +22,8 @@ var _ = Describe("CommitInfo struct", func() { BeforeEach(func() { filePaths = []*git.GitFilePathInfo{ { - SourcePath: "not_exist_file", - DestionationPath: dstPath, + SourcePath: "not_exist_file", + DestinationPath: dstPath, }} }) It("should return empty map", func() { @@ -42,8 +42,8 @@ var _ = Describe("CommitInfo struct", func() { Expect(err).Error().ShouldNot(HaveOccurred()) filePaths = []*git.GitFilePathInfo{ { - SourcePath: testFile.Name(), - DestionationPath: dstPath, + SourcePath: testFile.Name(), + DestinationPath: dstPath, }} }) It("should return empty map", func() { @@ -83,7 +83,7 @@ var _ = Describe("GenerateGitFileInfo func", func() { fileInfo, err := git.GenerateGitFileInfo([]string{tempFileLoc}, gitDir) Expect(err).ShouldNot(HaveOccurred()) Expect(len(fileInfo)).Should(Equal(1)) - Expect(fileInfo[0].DestionationPath).Should(Equal(filepath.Join(gitDir, tempFileLoc))) + Expect(fileInfo[0].DestinationPath).Should(Equal(filepath.Join(gitDir, tempFileLoc))) }) }) When("filePath type is dir", func() { @@ -92,7 +92,7 @@ var _ = Describe("GenerateGitFileInfo func", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(len(fileInfo)).Should(Equal(1)) fileName := filepath.Base(tempFileLoc) - Expect(fileInfo[0].DestionationPath).Should(Equal(filepath.Join(gitDir, fileName))) + Expect(fileInfo[0].DestinationPath).Should(Equal(filepath.Join(gitDir, fileName))) }) }) }) diff --git a/pkg/util/scm/git/repoinfo.go b/pkg/util/scm/git/repoinfo.go index 177059419..e3c51564b 100644 --- a/pkg/util/scm/git/repoinfo.go +++ b/pkg/util/scm/git/repoinfo.go @@ -16,11 +16,12 @@ type RepoInfo struct { Namespace string BaseURL string - // used for github + // used for GitHub WorkPath string NeedAuth bool } +// unused func (r *RepoInfo) GetRepoNameWithBranch() string { return fmt.Sprintf("%s-%s", r.Repo, r.Branch) } diff --git a/pkg/util/scm/git/state.go b/pkg/util/scm/git/state.go index 39d7fb58b..a3dd527ff 100644 --- a/pkg/util/scm/git/state.go +++ b/pkg/util/scm/git/state.go @@ -1,6 +1,7 @@ package git import ( + "crypto/md5" "crypto/sha1" "encoding/hex" "fmt" @@ -22,10 +23,17 @@ func (f *RepoFileStatus) EncodeToGitHubContentOption(commitMsg string) *github.R } } -func CaluateGitHubBlobSHA(fileContent string) string { +func CalculateGitHubBlobSHA(fileContent []byte) string { p := fmt.Sprintf("blob %d\x00", len(fileContent)) h := sha1.New() h.Write([]byte(p)) h.Write([]byte(fileContent)) return hex.EncodeToString(h.Sum(nil)) } + +// CalculateLocalFileSHA is used to calculate file content's md5 +func CalculateLocalFileSHA(fileContent []byte) string { + h := md5.New() + h.Write(fileContent) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/pkg/util/scm/git/state_test.go b/pkg/util/scm/git/state_test.go index a5f0d0f19..2430d72d7 100644 --- a/pkg/util/scm/git/state_test.go +++ b/pkg/util/scm/git/state_test.go @@ -37,6 +37,6 @@ var _ = Describe("CaluateBlobSHA func", func() { var content string It("should return as expect", func() { content = "test Content" - Expect(git.CaluateGitHubBlobSHA(content)).Should(Equal("d9c012c6ecfcc8ce04a6538cc43490b1d5401241")) + Expect(git.CalculateGitHubBlobSHA([]byte(content))).Should(Equal("d9c012c6ecfcc8ce04a6538cc43490b1d5401241")) }) }) diff --git a/pkg/util/scm/github/commit.go b/pkg/util/scm/github/commit.go index 4a8c3e9c4..544bdcf8f 100644 --- a/pkg/util/scm/github/commit.go +++ b/pkg/util/scm/github/commit.go @@ -3,11 +3,10 @@ package github import ( "fmt" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/google/go-github/v42/github" "github.com/devstream-io/devstream/pkg/util/log" + "github.com/devstream-io/devstream/pkg/util/scm/git" ) func (c *Client) GetLastCommit() (*github.RepositoryCommit, error) { @@ -27,10 +26,9 @@ func (c *Client) GetLastCommit() (*github.RepositoryCommit, error) { } func (c *Client) BuildCommitTree(ref *github.Reference, commitInfo *git.CommitInfo, checkChange bool) (*github.Tree, error) { - entries := []*github.TreeEntry{} + var entries []*github.TreeEntry for githubPath, content := range commitInfo.GitFileMap { - contentString := string(content) - if checkChange && !c.checkFileChange(githubPath, contentString) { + if checkChange && !c.checkFileChange(githubPath, content) { log.Debugf("Github File [%s] content not changed, not commit", githubPath) continue } diff --git a/pkg/util/scm/github/download.go b/pkg/util/scm/github/download.go index 777538ab2..54a26bace 100644 --- a/pkg/util/scm/github/download.go +++ b/pkg/util/scm/github/download.go @@ -60,7 +60,7 @@ func (c *Client) DownloadAsset(tagName, assetName, fileName string) error { } // 4. download - n, err := downloader.Download(downloadUrl, fileName, c.WorkPath) + n, err := downloader.New().WithProgressBar().Download(downloadUrl, fileName, c.WorkPath) if err != nil { log.Debugf("Failed to download asset from %s.", downloadUrl) return err diff --git a/pkg/util/scm/github/download_test.go b/pkg/util/scm/github/download_test.go index 107d94761..1d5f4323c 100644 --- a/pkg/util/scm/github/download_test.go +++ b/pkg/util/scm/github/download_test.go @@ -158,7 +158,7 @@ var _ = Describe("DownloadAsset", func() { Expect(ghClient).NotTo(Equal(nil)) err = ghClient.DownloadAsset(tagName, assetName, ".") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("failed to get the filename from url: %s", downloadUrl))) + Expect(err.Error()).To(ContainSubstring("filename must not be dir")) }) }) diff --git a/pkg/util/scm/github/file.go b/pkg/util/scm/github/file.go index 73a6b2f69..45980e563 100644 --- a/pkg/util/scm/github/file.go +++ b/pkg/util/scm/github/file.go @@ -10,6 +10,57 @@ import ( "github.com/devstream-io/devstream/pkg/util/scm/git" ) +// PushFiles will push local change to remote repo +// return boolean value is for control whether to roll out if encounter error +func (c *Client) PushFiles(commitInfo *git.CommitInfo, checkChange bool) (bool, error) { + // 1. create new branch from main + ref, err := c.NewBranch(commitInfo.CommitBranch) + if err != nil { + log.Warnf("Failed to create transit branch: %s", err) + return false, err + } + // delete new branch after func exit + defer func() { + err = c.DeleteBranch(commitInfo.CommitBranch) + if err != nil { + log.Warnf("Failed to delete transit branch: %s", err) + } + }() + tree, err := c.BuildCommitTree(ref, commitInfo, checkChange) + if err != nil { + log.Debugf("Failed to build commit tree: %s.", err) + return true, err + } + // if no new files to commit, just return + if tree == nil { + log.Successf("Github file all not change, pass...") + return false, nil + } + + // 2. push local file change to new branch + if err := c.PushLocalPath(ref, tree, commitInfo); err != nil { + log.Debugf("Failed to walk local repo-path: %s.", err) + return true, err + } + + // 3. merge new branch to main + if err = c.MergeCommits(commitInfo); err != nil { + log.Debugf("Failed to merge commits: %s.", err) + return true, err + } + // 4. delete placeholder file + if err = c.DeleteFiles(&git.CommitInfo{ + CommitMsg: "delete placeholder file", + CommitBranch: c.Branch, + GitFileMap: git.GitFileContentMap{ + repoPlaceHolderFileName: []byte{}, + }, + }); err != nil { + log.Debugf("github delete init file failed: %s", err) + } + return false, nil +} + func (c *Client) CreateFile(content []byte, filePath, targetBranch string) error { defaultMsg := "Initialize the repository" @@ -42,7 +93,7 @@ func (c *Client) PushLocalPath(ref *github.Reference, tree *github.Tree, commitI return err } -func (c *Client) GetLocationInfo(location string) ([]*git.RepoFileStatus, error) { +func (c *Client) GetPathInfo(location string) ([]*git.RepoFileStatus, error) { fileContent, directoryContent, resp, err := c.Client.Repositories.GetContents( c.Context, c.GetRepoOwner(), @@ -68,13 +119,13 @@ func (c *Client) GetLocationInfo(location string) ([]*git.RepoFileStatus, error) return gitfilesStatus, nil } -func (c *Client) checkFileChange(location string, content string) bool { - fileInfos, err := c.GetLocationInfo(location) +func (c *Client) checkFileChange(location string, content []byte) bool { + fileInfos, err := c.GetPathInfo(location) if err != nil { log.Debugf("Github request check file SHA failed: %s", err) return true } - contentSHA := git.CaluateGitHubBlobSHA(content) + contentSHA := git.CalculateGitHubBlobSHA(content) for _, f := range fileInfos { if f.SHA == contentSHA { return false @@ -85,9 +136,9 @@ func (c *Client) checkFileChange(location string, content string) bool { func (c *Client) DeleteFiles(commitInfo *git.CommitInfo) error { for fileLoc := range commitInfo.GitFileMap { - fileInfos, err := c.GetLocationInfo(fileLoc) + fileInfos, err := c.GetPathInfo(fileLoc) if err != nil || len(fileInfos) != 1 { - log.Successf("Github file %s already removed.", fileLoc) + log.Debugf("Github file %s already removed.", fileLoc) continue } opts := fileInfos[0].EncodeToGitHubContentOption( diff --git a/pkg/util/scm/github/file_test.go b/pkg/util/scm/github/file_test.go index 3de20d63d..07ad52a95 100644 --- a/pkg/util/scm/github/file_test.go +++ b/pkg/util/scm/github/file_test.go @@ -3,6 +3,9 @@ package github_test import ( "fmt" "net/http" + "os" + "strconv" + "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -18,6 +21,7 @@ var _ = Describe("Github files methods", func() { s *ghttp.Server repoName, owner, branch, reqPath string c *github.Client + commitInfo *git.CommitInfo ) BeforeEach(func() { repoName = "test_repo" @@ -29,6 +33,13 @@ var _ = Describe("Github files methods", func() { Branch: branch, Owner: owner, } + commitInfo = &git.CommitInfo{ + CommitMsg: "test", + CommitBranch: branch, + GitFileMap: map[string][]byte{ + "srcPath": []byte("test data"), + }, + } c = newTestClient(s.URL(), repoInfo) }) AfterEach(func() { @@ -57,7 +68,7 @@ var _ = Describe("Github files methods", func() { )) }) It("should work", func() { - info, err := c.GetLocationInfo(testFile) + info, err := c.GetPathInfo(testFile) Expect(err).ShouldNot(HaveOccurred()) Expect(info).ShouldNot(BeNil()) Expect(len(info)).Should(Equal(1)) @@ -79,10 +90,40 @@ var _ = Describe("Github files methods", func() { )) }) It("should return empty list", func() { - info, err := c.GetLocationInfo(testFile) + info, err := c.GetPathInfo(testFile) Expect(err).ShouldNot(HaveOccurred()) Expect(len(info)).Should(Equal(0)) }) }) }) + + Context("PushLocalPathToRepo", func() { + BeforeEach(func() { + s.Reset() + s.SetAllowUnhandledRequests(true) + }) + + It("1. create new branch from main", func() { + s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) + r, err := c.PushFiles(commitInfo, false) + Expect(err).NotTo(Succeed()) + Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) + Expect(r).To(Equal(false)) + }) + It("2. create new branch from main", func() { + // u := fmt.Sprintf("/repos/%v/%v/git/ref/heads/%s", org, repo, filePath) + u := fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repoName, strings.Trim(os.TempDir(), "/")) + s.Reset() + s.SetAllowUnhandledRequests(true) + s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) + s.RouteToHandler("GET", u, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "") + }) + r, err := c.PushFiles(commitInfo, false) + Expect(err).NotTo(Succeed()) + Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) + Expect(r).To(Equal(false)) + }) + }) + }) diff --git a/pkg/util/scm/github/repo.go b/pkg/util/scm/github/repo.go index d04f2879e..2e113d070 100644 --- a/pkg/util/scm/github/repo.go +++ b/pkg/util/scm/github/repo.go @@ -66,59 +66,8 @@ func (c *Client) DescribeRepo() (*github.Repository, error) { return repo, nil } -// PushLocalPathToBranch will push local change to remote repo -// return boolean value is for control whether to rollout if encounter error -func (c *Client) PushLocalFileToRepo(commitInfo *git.CommitInfo, checkChange bool) (bool, error) { - // 1. create new branch from main - ref, err := c.NewBranch(commitInfo.CommitBranch) - if err != nil { - log.Debugf("Failed to create transit branch: %s", err) - return false, err - } - // delete new branch after func exit - defer func() { - err = c.DeleteBranch(commitInfo.CommitBranch) - if err != nil { - log.Warnf("Failed to delete transit branch: %s", err) - } - }() - tree, err := c.BuildCommitTree(ref, commitInfo, checkChange) - if err != nil { - log.Debugf("Failed to build commit tree: %s.", err) - return true, err - } - // if no new files to commit, just return - if tree == nil { - log.Successf("Github file all not change, pass...") - return false, nil - } - - // 2. push local file change to new branch - if err := c.PushLocalPath(ref, tree, commitInfo); err != nil { - log.Debugf("Failed to walk local repo-path: %s.", err) - return true, err - } - - // 3. merge new branch to main - if err = c.MergeCommits(commitInfo); err != nil { - log.Debugf("Failed to merge commits: %s.", err) - return true, err - } - // 4. delete placeholder file - if err = c.DeleteFiles(&git.CommitInfo{ - CommitMsg: "delete placeholder file", - CommitBranch: c.Branch, - GitFileMap: git.GitFileContentMap{ - repoPlaceHolderFileName: []byte{}, - }, - }); err != nil { - log.Debugf("github delete init file failed: %s", err) - } - return false, nil -} - func (c *Client) InitRepo() error { - // It's ok to give the opts.Org to CreateRepo() when create a repository for a authenticated user. + // It's ok to give the opts.Org to CreateRepo() when create a repository for an authenticated user. if err := c.CreateRepo(c.Org, c.Branch); err != nil { // recreate if set tryTime log.Errorf("Failed to create repo: %s.", err) diff --git a/pkg/util/scm/github/repo_test.go b/pkg/util/scm/github/repo_test.go index 0d25ae260..be0cd440d 100644 --- a/pkg/util/scm/github/repo_test.go +++ b/pkg/util/scm/github/repo_test.go @@ -3,9 +3,7 @@ package github_test import ( "fmt" "net/http" - "os" "strconv" - "strings" githubCommon "github.com/google/go-github/v42/github" . "github.com/onsi/ginkgo/v2" @@ -21,11 +19,9 @@ var _ = Describe("Repo", func() { s *ghttp.Server rightClient, wrongClient *github.Client owner, repoName, org = "o", "r", "or" - commitInfo *git.CommitInfo ) // var rep *go_github.Repository defaultBranch := "db" - mergeBranch := "mb" mainBranch := "mab" rightOpt := &git.RepoInfo{ Owner: owner, @@ -44,13 +40,6 @@ var _ = Describe("Repo", func() { Expect(rightClient).NotTo(Equal(nil)) wrongClient, _ = github.NewClientWithOption(wrongOpt, s.URL()) Expect(wrongClient).NotTo(Equal(nil)) - commitInfo = &git.CommitInfo{ - CommitMsg: "test", - CommitBranch: mergeBranch, - GitFileMap: map[string][]byte{ - "srcPath": []byte("test data"), - }, - } }) AfterEach(func() { @@ -125,35 +114,6 @@ var _ = Describe("Repo", func() { }) }) - Context("PushLocalPathToRepo", func() { - BeforeEach(func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - }) - - It("1. create new branch from main", func() { - s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) - r, err := rightClient.PushLocalFileToRepo(commitInfo, false) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - Expect(r).To(Equal(false)) - }) - It("2. create new branch from main", func() { - // u := fmt.Sprintf("/repos/%v/%v/git/ref/heads/%s", org, repo, filePath) - u := fmt.Sprintf("/repos/%s/%s/contents/%s", org, repoName, strings.Trim(os.TempDir(), "/")) - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) - s.RouteToHandler("GET", u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "") - }) - r, err := rightClient.PushLocalFileToRepo(commitInfo, false) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - Expect(r).To(Equal(false)) - }) - }) - Context("InitRepo", func() { It("CreateRepo with status 500", func() { s.Reset() diff --git a/pkg/util/scm/gitlab/commit.go b/pkg/util/scm/gitlab/commit.go index 197a724cb..64f1c2651 100644 --- a/pkg/util/scm/gitlab/commit.go +++ b/pkg/util/scm/gitlab/commit.go @@ -1,58 +1,72 @@ package gitlab import ( + "github.com/imdario/mergo" "github.com/xanzy/go-gitlab" - "github.com/devstream-io/devstream/pkg/util/pkgerror" + "github.com/devstream-io/devstream/pkg/util/log" "github.com/devstream-io/devstream/pkg/util/scm/git" ) -func (c *Client) PushLocalFileToRepo(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) { - // if checkUpdate is true, check files to update first - if checkUpdate { - updateCommitInfo := &git.CommitInfo{ - CommitMsg: commitInfo.CommitMsg, - CommitBranch: commitInfo.CommitBranch, - } - commitMap := make(git.GitFileContentMap) - for filePath, content := range commitInfo.GitFileMap { - fileExist, err := c.FileExists(filePath) - if err != nil { - return false, err - } - if fileExist { - commitMap[filePath] = content - delete(commitInfo.GitFileMap, filePath) - } - } - if len(commitMap) > 0 { - err := c.UpdateFiles(updateCommitInfo) - if err != nil { - return true, err - } +type commitTree struct { + commitMessage string + commitBranch string + gitlabFileMap map[gitlab.FileActionValue]git.GitFileContentMap +} + +func newCommitTree(commitMessage string, branch string) *commitTree { + return &commitTree{ + commitMessage: commitMessage, + commitBranch: branch, + gitlabFileMap: make(map[gitlab.FileActionValue]git.GitFileContentMap), + } +} + +func (t *commitTree) addCommitFile(action gitlab.FileActionValue, scmPath string, content []byte) { + actionMap, ok := t.gitlabFileMap[action] + if !ok { + t.gitlabFileMap[action] = git.GitFileContentMap{ + scmPath: content, } + } else { + actionMap[scmPath] = content } - createCommitOptions := c.CreateCommitInfo(gitlab.FileCreate, commitInfo) - _, _, err := c.Commits.CreateCommit(c.GetRepoPath(), createCommitOptions) - if err != nil && !pkgerror.CheckSlientErrorByMessage(err, errFileExist) { - return true, c.newModuleError(err) +} + +func (t *commitTree) addCommitFilesFromMap(action gitlab.FileActionValue, gitMap git.GitFileContentMap) { + actionMap, ok := t.gitlabFileMap[action] + if !ok { + t.gitlabFileMap[action] = gitMap + } else { + err := mergo.Merge(&actionMap, gitMap, mergo.WithOverride) + if err != nil { + log.Debugf("gitlab add commit files failed: %+v", err) + } } - return false, nil } -func (c *Client) CreateCommitInfo(action gitlab.FileActionValue, commitInfo *git.CommitInfo) *gitlab.CreateCommitOptions { - var commitActionsOptions = make([]*gitlab.CommitActionOptions, 0, len(commitInfo.GitFileMap)) +func (t *commitTree) getFilesCount() int { + var fileCount int + for _, files := range t.gitlabFileMap { + fileCount += len(files) + } + return fileCount +} - for fileName, content := range commitInfo.GitFileMap { - commitActionsOptions = append(commitActionsOptions, &gitlab.CommitActionOptions{ - Action: gitlab.FileAction(action), - FilePath: gitlab.String(fileName), - Content: gitlab.String(string(content)), - }) +func (t *commitTree) createCommitInfo() *gitlab.CreateCommitOptions { + var commitActionsOptions = make([]*gitlab.CommitActionOptions, 0, t.getFilesCount()) + for action, fileMap := range t.gitlabFileMap { + for fileName, content := range fileMap { + commitActionsOptions = append(commitActionsOptions, &gitlab.CommitActionOptions{ + Action: gitlab.FileAction(action), + FilePath: gitlab.String(fileName), + Content: gitlab.String(string(content)), + }) + } } return &gitlab.CreateCommitOptions{ - Branch: gitlab.String(c.Branch), - CommitMessage: gitlab.String(commitInfo.CommitMsg), + Branch: gitlab.String(t.commitBranch), + CommitMessage: gitlab.String(t.commitMessage), Actions: commitActionsOptions, } } diff --git a/pkg/util/scm/gitlab/commit_test.go b/pkg/util/scm/gitlab/commit_test.go index 8935ca631..105c95e38 100644 --- a/pkg/util/scm/gitlab/commit_test.go +++ b/pkg/util/scm/gitlab/commit_test.go @@ -53,19 +53,19 @@ var _ = Describe("commit method", func() { RepoInfo: repoInfo, } }) - Context("CreateCommitInfo method", func() { - It("should return gitlab commit options", func() { - commitInfoData := gitlabClient.CreateCommitInfo(gitlabCommon.FileCreate, commitInfo) - Expect(*commitInfoData.Branch).Should(Equal(branch)) - Expect(*commitInfoData.CommitMessage).Should(Equal(commitMsg)) - actions := commitInfoData.Actions - Expect(len(actions)).Should(Equal(1)) - action := actions[0] - Expect(*action.Action).Should(Equal(gitlabCommon.FileCreate)) - Expect(*action.FilePath).Should(Equal(gitFile)) - Expect(*action.Content).Should(Equal(gitFileContent)) - }) - }) + // Context("CreateCommitInfo method", func() { + // It("should return gitlab commit options", func() { + // commitInfoData := gitlabClient.CreateCommitInfo(gitlabCommon.FileCreate, commitInfo) + // Expect(*commitInfoData.Branch).Should(Equal(branch)) + // Expect(*commitInfoData.CommitMessage).Should(Equal(commitMsg)) + // actions := commitInfoData.Actions + // Expect(len(actions)).Should(Equal(1)) + // action := actions[0] + // Expect(*action.Action).Should(Equal(gitlabCommon.FileCreate)) + // Expect(*action.FilePath).Should(Equal(gitFile)) + // Expect(*action.Content).Should(Equal(gitFileContent)) + // }) + // }) Context("CommitActions method", func() { BeforeEach(func() { @@ -75,13 +75,11 @@ var _ = Describe("commit method", func() { )) }) It("should work normal", func() { - needRollBack, err := gitlabClient.PushLocalFileToRepo(commitInfo, false) + needRollBack, err := gitlabClient.PushFiles(commitInfo, false) Expect(err).Error().ShouldNot(HaveOccurred()) Expect(needRollBack).Should(BeFalse()) err = gitlabClient.DeleteFiles(commitInfo) Expect(err).Error().ShouldNot(HaveOccurred()) - err = gitlabClient.UpdateFiles(commitInfo) - Expect(err).Error().ShouldNot(HaveOccurred()) }) }) AfterEach(func() { diff --git a/pkg/util/scm/gitlab/file.go b/pkg/util/scm/gitlab/file.go index 2f98b76b8..3321f03c4 100644 --- a/pkg/util/scm/gitlab/file.go +++ b/pkg/util/scm/gitlab/file.go @@ -11,25 +11,42 @@ import ( "github.com/devstream-io/devstream/pkg/util/scm/git" ) -func (c *Client) DeleteFiles(commitInfo *git.CommitInfo) error { - deleteCommitoptions := c.CreateCommitInfo(gitlab.FileDelete, commitInfo) - _, _, err := c.Commits.CreateCommit(c.GetRepoPath(), deleteCommitoptions) - if err != nil && !pkgerror.CheckSlientErrorByMessage(err, errRepoNotFound) { - return c.newModuleError(err) +func (c *Client) PushFiles(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) { + // if checkUpdate is true, check files to update first + tree := newCommitTree(commitInfo.CommitMsg, c.Branch) + if checkUpdate { + for scmPath, content := range commitInfo.GitFileMap { + fileExist, err := c.checkFileExist(scmPath) + if err != nil { + return false, err + } + if fileExist { + tree.addCommitFile(gitlab.FileUpdate, scmPath, content) + } else { + tree.addCommitFile(gitlab.FileCreate, scmPath, content) + } + } + } else { + tree.addCommitFilesFromMap(gitlab.FileCreate, commitInfo.GitFileMap) } - return nil + _, _, err := c.Commits.CreateCommit(c.GetRepoPath(), tree.createCommitInfo()) + if err != nil && !pkgerror.CheckSlientErrorByMessage(err, errFileExist) { + return true, c.newModuleError(err) + } + return false, nil } -func (c *Client) UpdateFiles(commitInfo *git.CommitInfo) error { - updateCommitoptions := c.CreateCommitInfo(gitlab.FileUpdate, commitInfo) - _, _, err := c.Commits.CreateCommit(c.GetRepoPath(), updateCommitoptions) - if err != nil { +func (c *Client) DeleteFiles(commitInfo *git.CommitInfo) error { + tree := newCommitTree(commitInfo.CommitMsg, c.Branch) + tree.addCommitFilesFromMap(gitlab.FileDelete, commitInfo.GitFileMap) + _, _, err := c.Commits.CreateCommit(c.GetRepoPath(), tree.createCommitInfo()) + if err != nil && !pkgerror.CheckSlientErrorByMessage(err, errRepoNotFound) { return c.newModuleError(err) } return nil } -func (c *Client) FileExists(filename string) (bool, error) { +func (c *Client) checkFileExist(filename string) (bool, error) { getFileOptions := &gitlab.GetFileOptions{ Ref: gitlab.String(c.Branch), } @@ -48,7 +65,7 @@ func (c *Client) FileExists(filename string) (bool, error) { return true, nil } -func (c *Client) GetLocationInfo(path string) ([]*git.RepoFileStatus, error) { +func (c *Client) GetPathInfo(path string) ([]*git.RepoFileStatus, error) { gitRepoFileStatus := make([]*git.RepoFileStatus, 0) getFileOptions := &gitlab.GetFileOptions{ Ref: gitlab.String(c.Branch), diff --git a/pkg/util/scm/gitlab/file_test.go b/pkg/util/scm/gitlab/file_test.go index c202363c2..84a6225fe 100644 --- a/pkg/util/scm/gitlab/file_test.go +++ b/pkg/util/scm/gitlab/file_test.go @@ -39,35 +39,7 @@ var _ = Describe("CreateCommitInfo method", func() { } server.SetAllowUnhandledRequests(true) }) - Context("FileExists method", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects/%s/%s/repository/files/%s", apiRootPath, owner, repoName, testFile) - }) - When("get files url return err", func() { - BeforeEach(func() { - server.SetUnhandledRequestStatusCode(http.StatusNotFound) - }) - It("should return false", func() { - exist, err := gitlabClient.FileExists(testFile) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(exist).Should(BeFalse()) - }) - }) - When("git files return normal", func() { - BeforeEach(func() { - server.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusOK, nil), - )) - }) - It("should return true", func() { - exist, err := gitlabClient.FileExists(testFile) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(exist).Should(BeTrue()) - }) - }) - }) - Context("GetLocationInfo method", func() { + Context("GetPathInfo method", func() { When("gitlab return normal", func() { BeforeEach(func() { reqPath = fmt.Sprintf("%sprojects/%s/%s/repository/files/%s", apiRootPath, owner, repoName, testFile) @@ -77,7 +49,7 @@ var _ = Describe("CreateCommitInfo method", func() { )) }) It("should work", func() { - fileInfo, err := gitlabClient.GetLocationInfo(testFile) + fileInfo, err := gitlabClient.GetPathInfo(testFile) Expect(err).Error().ShouldNot(HaveOccurred()) Expect(fileInfo).ShouldNot(BeNil()) Expect(fileInfo[0].Branch).Should(BeEmpty()) @@ -92,7 +64,7 @@ var _ = Describe("CreateCommitInfo method", func() { )) }) It("should return err", func() { - _, err := gitlabClient.GetLocationInfo(testFile) + _, err := gitlabClient.GetPathInfo(testFile) Expect(err).Error().Should(HaveOccurred()) }) }) @@ -106,7 +78,7 @@ var _ = Describe("CreateCommitInfo method", func() { )) }) It("should work normal", func() { - _, err := gitlabClient.GetLocationInfo(testFile) + _, err := gitlabClient.GetPathInfo(testFile) Expect(err).Error().Should(HaveOccurred()) }) diff --git a/test/e2e/yaml/e2e-tools.yaml b/test/e2e/yaml/e2e-tools.yaml index b62c620ca..dd172c34f 100644 --- a/test/e2e/yaml/e2e-tools.yaml +++ b/test/e2e/yaml/e2e-tools.yaml @@ -47,7 +47,10 @@ tools: namespace: [[ argocdNameSpace ]] wait: true timeout: [[ argocdDeployTimeout ]] - upgradeCRDs: true + upgradeCRDs: false + valuesYaml: | + crds: + keep: false - name: argocdapp instanceID: default dependsOn: ["argocd.default", "repo-scaffolding.golang-github"]