Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export KUBECONFIG=<path>

`pkg/operator/interface.go` 定义了仅一个方法的接口 `OperatorInterface.UpgradeOperator`。Factory(`factory.go`)根据 `operatorConfig.type` 选择实现:

- **`operatorhub`(默认)** —— `pkg/operator/operatorhub/`。生产路径。通过 dynamic client + `unstructured` 操作三类资源
- Alauda 自定义资源 `app.alauda.io/v1alpha1` 下的 `Artifact` / `ArtifactVersion`(系统命名空间硬编码为 `cpaas-system`,OLM 源名硬编码为 `platform`
- **`operatorhub`(默认)** —— `pkg/operator/operatorhub/`。生产路径。Artifact / ArtifactVersion 的**写路径**已外包给 `violet` 二进制(子进程,见 `violet.go::installViaViolet`);Go 侧仍用 dynamic client 做以下三类只读 / OLM 资源
- Alauda 自定义资源 `app.alauda.io/v1alpha1` 下的 `Artifact` / `ArtifactVersion`:**Go 端只剩 Get + Delete**(清理残留 AV、等 AV phase=Present、读 status.version 拿 CSV);Create / Patch / Update 全归 violet。命名空间硬编码 `cpaas-system`,OLM 源名硬编码 `platform`(const `targetCatalogSource` in `operator.go`)。
- OLM 标准资源 `Subscription` / `InstallPlan` / `ClusterServiceVersion` / `PackageManifest`
- 升级流程:`InstallArtifactVersion`(创建 ArtifactVersionphase=Present → 等 PackageManifest 出现对应 CSV)→ `InstallSubscription`(先删旧 Sub + 旧 CSV → 创建 Subscription `installPlanApproval: Manual` → 等 InstallPlan → 把 `spec.approved=true` → 等 CSV phase=Succeeded
- 升级流程:`InstallArtifactVersion`(下载 .tgz可选 sha256 校验 → 删除同名 AV 残留 → 调 `violet push` → 等 AV phase=Present 并 cross-check spec.tag → 等 PackageManifest 出现对应 CSV)→ `InstallSubscription`:**Subscription 不存在则创建**(`installPlanApproval: Manual`, `startingCSV=目标 CSV`);**Subscription 已存在则 in-place refresh**(必要时 patch `spec.channel`,并总是 bump `upgrade-test.alauda.io/refresh-trigger` 注解强制 OLM 重新 reconcile,**不删除、不重建**——依赖 OLM replace chain 滚动到新 CSV)→ 等 InstallPlan(用 `waitInstallPlanForCSV` 匹配 `spec.clusterServiceVersionNames` 包含目标 CSV,避免拿到 `status.installplan.name` 短暂指向的旧 IP)→ patch `spec.approved=true` → 等 CSV phase=Succeeded
- **`local`** —— `pkg/operator/local/operator.go`。本地开发用,直接在 workspace 里跑 `make deploy`(可被 `operatorConfig.command` 覆盖),不接 OLM。

新增 Operator 类型时:在 `pkg/operator/` 下加子包实现 `OperatorInterface`,并在 `factory.go` 的 `CreateOperator` switch 中注册。
Expand All @@ -54,12 +54,19 @@ Artifact 名称约定:未显式给 `artifact` 字段时,自动拼成 `<artif

## 写代码时要注意的硬约束

- **不要把命名空间或 OLM source 写成参数** —— 当前实现里 `cpaas-system` 和 `source: platform` 是硬编码常量,跨 Operator 共用。如果业务上需要支持其他来源,先和用户确认。
- **Artifact / ArtifactVersion 写路径由 violet 子进程负责** —— Go 端只剩 Get + Delete。如果未来要重新加 Create / Patch / Update 必须先和用户确认是否要回退 violet 委托。`violet.go::installViaViolet` 是唯一调用点。
- **不要把命名空间或 OLM source 写成参数** —— 当前实现里 `cpaas-system` 和 `source: platform`(`const targetCatalogSource` in `operator.go`)是硬编码常量,跨 Operator 共用,并与 violet 命令 `--target-catalog-source` 共享同一来源。如果业务上需要支持其他来源,先和用户确认。
- **InstallPlan 一定是手动审批模式** —— `installPlanApproval: "Manual"`,由 `InstallSubscription` 自己 patch `spec.approved=true`。不要改成 Automatic,否则升级时序会乱。
- **新加 GVR 时统一在 `operator.go` 顶部声明** —— 见 `artifactGVR` / `subscriptionGVR` 等,不要在调用点 inline。
- **轮询用 `wait.PollUntilContextTimeout`** —— 间隔/超时都从 `OperatorConfig.Interval` / `Timeout` 拿,不要写常量。
- **日志走 knative `logging.FromContext(ctx)`** —— `cmd/upgrade_command.go` 在 ctx 上注入了 zap sugar logger,子包不要再自己起 logger。
- **YAML 字段大小写**:`config.go` 用的是 `yaml:"xxx,omitempty"` 小写驼峰(`operatorConfig`、`upgradePaths`、`bundleVersion`),demo 配置与 README 也是这套;改字段时同步改三处。
- **YAML 字段大小写**:`config.go` 用的是 `yaml:"xxx,omitempty"` 小写驼峰(`operatorConfig`、`upgradePaths`、`bundleVersion`、`violet`),demo 配置与 README 也是这套;改字段时同步改三处。
- **凭证注入:platform 双通道,registry 单通道** —— platform 凭证可走 `Violet.PlatformUsername` / `Violet.PlatformPassword`(config,yaml 字段 `platformUsername` / `platformPassword`)**或** `VIOLET_PLATFORM_USERNAME` / `VIOLET_PLATFORM_PASSWORD`(env),由 `BuildVioletPushArgs` 自动追加进 violet argv;两路都设置时 **config 优先,env 兜底**。registry 凭证仍只走环境变量 `VIOLET_REGISTRY_USERNAME` / `VIOLET_REGISTRY_PASSWORD`。写入 config 的凭证会随 yaml 进 git,敏感场景请用 env 注入。日志层 mask `--password` 和 `--platform-password` 后的值(`isPasswordFlag`),同时 `execVioletPush` 把解析后(config 或 env)的 platform 密码加入 `RedactSecrets` 以擦掉 violet 自身 stdout/stderr 的回显。`pkg/exec.Command.EnvAllowlist` 限制子进程 env 范围,调 violet 时只透传 `KUBECONFIG` / `PATH` / `HOME` / `USER` / `VIOLET_*`。
- **CLI 永远给 violet 传 `--force`** —— 不传时 violet 误判 "already exist, skip it" 直接 no-op,导致 wait AV Present 超时。upgrade CLI 已经在 `installViaViolet` 里 ensure-clean 删除残留 AV,所以"保留 stale AV"不是合法诉求,没必要开放 Force 配置字段。
- **`Violet.Clusters` 多集群必填** —— violet 默认写 `global` 子集群,跟 kubectl 实际连的子集群(如 `/kubernetes/devops`)可能不一致;不填会出现 "violet 报 success 但 AV 在 kubectl 看不到" 的静默假成功。
- **`Violet.PushArgs` 不允许写凭证 flag** —— `--username` / `--password` / `--platform-username` / `--platform-password` 都被 `BuildVioletPushArgs` 拒绝(包含 `--flag=value` 形式)。凭证必须走专门的注入入口(platform:`Violet.PlatformUsername`/`PlatformPassword` 或 `VIOLET_PLATFORM_*` env;registry:`VIOLET_REGISTRY_*` env),不准从 `PushArgs` 偷塞,否则会绕过日志屏蔽与 `RedactSecrets`。
- **`packagePrefix` 是必填字段,无默认值** —— MinIO 根地址跨环境不同,CLI 拒绝硬编码任何默认。空值会在 `BuildPackageURL` 阶段返回 "packagePrefix is empty" 错误。
- **`Violet.LocalPackageDir` 是可选的本地 .tgz 缓存根** —— 非空时 `acquirePackage` 用 `<LocalPackageDir>/<operatorName>/<packageChannel>/<operatorName>.latest.ALL.<bundleVersion>.tgz` 这个 mirror MinIO URL 的布局检查缓存:命中跳过 HTTP;miss 直接下载到该路径(父目录自动 mkdir),不再走 `/tmp`,下次自动命中;任一路径下都 **不会清理**(cleanup 为 noop),保留为缓存。`VerifySha256` 即使命中也会执行——避免损坏的缓存文件被静默喂给 violet。留空保持旧行为:下载到一次性 `/tmp/upgrade-violet-*` 并在 `defer cleanup()` 中删除,无跨次复用。下载半途失败时 cache 路径的半成品会被 `os.Remove` 清掉,防止下次假命中。

## PR / 协作流程

Expand Down
66 changes: 64 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ mv upgrade-ubuntu-latest-amd64 upgrade && chmod +x upgrade
"""
url: http://test-gitlab-upgrade.example.com
username: root
password: 07Apples@
password: redacted-password
timeout: 15m
importProjectPath: ./testdata/resources/test-upgrade-repo_export.tar.gz
"""
Expand All @@ -89,21 +89,42 @@ operatorConfig:
workspace: /app/testing/ # test case 执行位置
namespace: gitlab-ce-operator # operator 部署 ns
name: gitlab-ce-operator # operator 名称
violet: # 必填:上架走外部 violet 二进制
packagePrefix: http://package-minio.alauda.cn:9199/packages/ # 必填,无默认值
platformAddress: https://my-acp.example.com # 必填:ACP 平台 URL
clusters: devops # 写入的目标子集群名;默认 "global",多集群部署必填
# bin: /usr/local/bin/violet # 可选;为空则在 $PATH 查 `violet`
# skipPush: true # 默认 true;私有 registry 场景置 false
# pushArgs: # 私有场景透传给 `violet push` 的额外非凭证参数
# - --dest-repo # 凭证 (--username/--password/--platform-username/--platform-password)
# - registry.private/devops # 必须走环境变量,pushArgs 写入会被 CLI 拒绝
# - --plain
# - --image-pull-secret
# - private-pull
upgradePaths: # 定义升级路径,可以包含多个
- name: v17.8 upgrade to v17.11 # 升级名称
versions: # 定义升级路经
- name: v17.8 # 版本名称
testCommand: |
TAGS=@prepare-17.8 GODOG_ARGS="--godog.format=allure" make test
bundleVersion: v17.8.10
channel: stable
channel: stable # OLM Subscription.spec.channel
# packageChannel: v17 # MinIO URL 段;与 OLM channel 不同时填,省略则 fallback 到 channel
# expectedSha256: a3f... # 可选;非空时强制校验下载的 .tgz
- name: v17.11 # 版本名称
testCommand: |
TAGS=@upgrade-17.11 GODOG_ARGS="--godog.format=allure --bdd.cleanup=false" make test
bundleVersion: v17.11.1
channel: stable
```

**配置说明**:

- **`operatorConfig.violet` 必填**。upgrade CLI 不再在 Go 代码里直接创建 `Artifact`/`ArtifactVersion` CR,而是把这步外包给 `violet` 二进制。`operatorConfig.violet` 为空时 `InstallArtifactVersion` 会立即返回配置错误。
- **`packagePrefix` 没有默认值**。MinIO 根地址跨环境(私有 / 共享 / 区域镜像)不同,CLI 拒绝硬编码任何值。
- **`.tgz` URL 拼接约定**:`<prefix>/<name>/<packageChannel>/<name>.latest.ALL.<bundleVersion>.tgz`。`packageChannel` 是 MinIO 仓库的路径段(如 `v4.0` / `v4.6` / `rc`),**与 OLM Subscription 的 `channel`(如 `stable` / `pipelines-4.0`)不是同一个概念**。当两者字面相同时(少数情况)可只填 `channel`,CLI 会自动 fallback;当两者不同(tektoncd 这类)必须显式填 `packageChannel`。
- **`expectedSha256` 强烈建议为外网下载场景填上**。HTTP 明文 + DNS 投毒可能注入恶意 tgz,违 violet 会带集群凭证把恶意 bundle 上架——内网 MinIO 也建议加防。

### 构建测试镜像

需要在测试镜像中添加升级测试的制品:
Expand Down Expand Up @@ -149,3 +170,44 @@ CMD ["--godog.concurrency=2", "--godog.format=allure", "--godog.tags=@prepare-17
export KUBECONFIG=<kubeconfig.yaml>
./upgrade --config upgrade.yaml
```

## violet 依赖与运行环境

upgrade CLI 把 `Artifact` / `ArtifactVersion` CR 的创建步骤外包给 `violet` 二进制,因此运行环境必须满足以下条件。

### 二进制依赖

- 运行环境 `$PATH` 上需要有可执行的 `violet`,或者通过 `operatorConfig.violet.bin` 指定绝对路径。Bin 非空时 CLI 会做 `filepath.IsAbs` + `os.Stat` + 可执行位校验,不接受相对路径或非可执行文件。
- 流水线场景:确保 Tekton task 镜像里预装 violet,或在 task 启动 script 里下载安装。本地开发:参考仓库内 `download-violet` skill 或自行从 cloud.alauda.cn 下载。
- 当前实现不做 `violet --version` preflight 检查,依赖 OS "command not found" 错误兜底。

### 子进程环境变量 allowlist

upgrade CLI 调用 violet 时**只透传**以下宿主环境变量:`KUBECONFIG`、`PATH`、`HOME`、`USER`、`VIOLET_*` 前缀。CI runner 上其他 secret(`GITHUB_TOKEN` / `AWS_*` / 等)不会进入 violet 子进程,符合最小权限原则。

### 凭证(两套环境变量)

`violet push` 涉及两类凭证,**都不写进 config.yaml**,改用环境变量:

| 用途 | 环境变量 | 注入的 violet 参数 | 何时需要 |
|------|----------|-------------------|----------|
| 登录 ACP 平台创建 Artifact/AV CR | `VIOLET_PLATFORM_USERNAME` / `VIOLET_PLATFORM_PASSWORD` | `--platform-username` / `--platform-password` | **真实集群必填**(violet 拒绝无凭证启动) |
| 推送镜像到私有 registry | `VIOLET_REGISTRY_USERNAME` / `VIOLET_REGISTRY_PASSWORD` | `--username` / `--password` | 仅 `skipPush: false` 私有场景 |

```sh
export VIOLET_PLATFORM_USERNAME=admin@example.invalid
export VIOLET_PLATFORM_PASSWORD=<password>
# 仅私有 push 场景需要的额外两行:
# export VIOLET_REGISTRY_USERNAME=<user>
# export VIOLET_REGISTRY_PASSWORD=<password>

./upgrade --config upgrade.yaml
```

upgrade CLI 检测到非空时自动追加到 `violet push` 的 argv,日志渲染时把 `--password` 和 `--platform-password` 之后的值替换成 `***`。

⚠️ **共享 CI runner 安全警告**:当前 violet 不支持 stdin / 文件方式读凭证,`--password` 只能进 argv。OS 级 `ps auxe` / `/proc/<pid>/cmdline` / `strace` 都能看到明文密码。**多租户共享 runner 上禁用 `skipPush: false`**,私有 push 场景务必使用独占的 pipeline runner / 独占的 OS 用户账号。

### k8s 凭证

violet 通过 `KUBECONFIG` 连集群(与 upgrade CLI 自身共享同一份)。流水线场景在调 upgrade 前先 `export KUBECONFIG=...` 即可,CLI 透传给子进程不需要额外配置。
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@ require (
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
Expand All @@ -37,9 +43,13 @@ require (
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.33.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
Expand Down
12 changes: 10 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
Expand All @@ -25,6 +27,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand All @@ -50,6 +54,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down Expand Up @@ -86,8 +94,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
Expand Down
Loading
Loading