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
14 changes: 14 additions & 0 deletions import-dashboards-terraform/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:1.24-alpine AS build

RUN apk add --no-cache git
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY import.go .
RUN go build -o main .

FROM hashicorp/terraform:1.13

WORKDIR /app
COPY --from=build /app/main .
ENTRYPOINT ["/app/main"]
43 changes: 43 additions & 0 deletions import-dashboards-terraform/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
IMAGE_NAME ?= import-tf-script
SIGNALFX_API_URL ?= https://app.us0.signalfx.com
RELATIVE_DIR_PATH ?= "generated-dashboards"


ifneq (${MAKEFILE_DIR_LOCATION},${WORKING_DIR})

%:
$(MAKE) -C ${MAKEFILE_DIR_LOCATION} $@

.PHONY: %

else

.PHONY: tidy
tidy:
go mod tidy

.PHONY: fmt
fmt:
go fmt ./...

.PHONY: build
build:
docker buildx build -t $(IMAGE_NAME) .

.PHONY: import-dashboard-group
import-dashboard-group: clean build
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be worth listing out in the readme what env vars users have available for setting things like their url (realm) and token and so forth?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a section for this

@docker run --volume="$(shell pwd):/terraform-state" \
-it $(IMAGE_NAME) --api-url $(SIGNALFX_API_URL) \
--api-token=$(SIGNALFX_AUTH_TOKEN) \
--groups $(GROUP_IDS) \
--dir /terraform-state/$(RELATIVE_DIR_PATH) \
--add-var-file \
--add-versions-file \
--allow-chart-name-conflict

.PHONY: clean
clean:
@echo "Cleaning up containers and image for $(IMAGE_NAME)"
@docker ps -a -q --filter ancestor=$(IMAGE_NAME) | xargs -r docker rm -f 2>/dev/null || true
@docker rmi $(IMAGE_NAME) -f 2>/dev/null || true
endif
123 changes: 123 additions & 0 deletions import-dashboards-terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Terraform Import SignalFx Dashboards

This folder contains helper scripts for importing SignalFx dashboard groups. along with their linked dashboards and charts, into Terraform code. This tool enables users to create dashboards in the SignalFx UI and then convert them into infrastructure-as-code for better version control and automation.

## Usage

This script, given dashboard group ids, will generate mostly usable terraform code for dashboard groups, and any associated dashboards/charts.

### Quick Start

For a quick start cmd to generate terraform code for dashboard group, run the following make command:

```sh
SIGNALFX_AUTH_TOKEN=<SIGNALFX_API_TOKEN> SIGNALFX_API_URL=<API_URL> GROUP_IDS=ID1,ID2,ID3 RELATIVE_DIR_PATH=resources/Dashboards make import-dashboard-group
```

**Note**: Angle brackets `< >` in examples indicate placeholders that should be replaced with actual values.

This will generate a bunch of `.tf` files. There will be a file for each dashboard group, and a different file for dashboards in the groups. The dashboard file will have the dashboard and chart resources.

### Environment Variables

The following environment variables can be used with the `make import-dashboard-group` command:

- `SIGNALFX_AUTH_TOKEN` (required): Your Splunk Observability API token
- `SIGNALFX_API_URL` (optional): The API URL for your realm. Defaults to `https://app.us0.signalfx.com`. Common values:
- US0: `https://app.us0.signalfx.com` (default)
- US1: `https://app.us1.signalfx.com`
- EU0: `https://app.eu0.signalfx.com`
- `GROUP_IDS` (required): Comma-separated list of dashboard group IDs without spaces (e.g., `GJsIuOtA4Ag,ABC123def,XYZ789ghi`)
- `RELATIVE_DIR_PATH` (optional): Directory path where generated files will be written. Defaults to `"generated-dashboards"`

### Building the Docker Image

To build docker image with the script binary and terraform installed, run the following command:

```sh
make build
```

It is recommeded to pull the latest version of the repo and run build cmd before running the script.

### Cleaning up the Docker Image

To clean up the docker image, run the following command:

```sh
make clean
```

### Viewing Options

To look at the options for the script, run the following command:

```sh
docker run import-tf-script --help
```

Currently supported options -

```
❯ docker run import-tf-script --help
Usage: /app/main [options]
-add-var-file
Add variables.tf file to the directory. This file only defines variabled the generated terraform code uses.
-add-versions-file
Add versions.tf file to the directory. This file will have terraform block with version requirements.
-allow-chart-name-conflict
Allow charts with the same name in a dashboard. This will add an index at the end of the chart's name attribute and resource name to resolve conflict in naming. It is recommended to rename charts when you export the dashboards to TF for the first time.
-api-token string
API token for authentication
-api-url string
The API URL for Splunk Observability (default "https://app.us0.signalfx.com")
-dir string
Working directory where TF files will be written.
-groups string
Comma-separated list dashboard group IDs in Splunk Observability to import. Required.
-tf-path string
The path to the Terraform binary (default "terraform")
```

### Example to run with custom options

```sh
cd ${REPO_ROOT}
docker run -v $(pwd):/app import-tf-script \
--api-token <TOKEN> \
--api-url <API_URL> \
--groups ID1,ID2,ID3 \
--dir /app/resources/APM \
--allow-chart-name-conflict
```

**Note**:
- Angle brackets `< >` in examples indicate placeholders that should be replaced with actual values

### Supported Chart Types

The following chart types are supported by the import script:
- **Heatmap** - `signalfx_heatmap_chart`
- **List** - `signalfx_list_chart`
- **SingleValue** - `signalfx_single_value_chart`
- **Text** - `signalfx_text_chart`
- **TimeSeriesChart** - `signalfx_time_chart`
- **TableChart** - `signalfx_table_chart`
- **Line** - `signalfx_line_chart`
- **Event** - `signalfx_event_feed_chart`

The following chart types are **NOT** currently supported and will cause the script to fail with an "unsupported chart type" error:
- **Log query charts** - Any charts that query log data for e.g. the `signalfx_log_timeline` chart

### Additional Notes

- The resource type and name must be unique within a TF module. Changing the resource name will be treated as a recreate by TF, which means dashboard/chart urls will be updated if their corresponding resource name is changed. We derive the TF resource name from the SignalFx object names. It is recommended to clean-up sfx names before the first import to avoid recreates when rerunning the script to import updates to dashboards. The resource names generated for the different resource types are explained below. The names will have non-alphanumeric characters replaced with `_`:
- `signalfx_dashboard_group`: `<dashboard group name>`
- `signalfx_dashboard`: `<dashboard group resource name>_<dashboard name>`
- `signalfx_*_chart`: `<dashboard resource name>_<chart name>`
- In case of duplicate chart type and name combos in a dashboards, the script will append a number to the chart's name attribute and the chart resource name to make it unique if the option `--allow-chart-name-conflict` is provided. The additional index in the conflicting chart name on the first run of the script, will ensure the re-runs of the script does not cause TF to recreate chart resource, keeping the url same. If the option is not provided, the script will exit with an error on encountering conflicts. It is recommended to rename charts to make them unique before running the script.
- For `text` charts, the script will fallback to the chart description to generate chart resource name if the chart name is empty. This is useful for Text charts which usually are untitled. Users are recommended to update their text charts to add some small unique description. We recommend using descriptions for text chart since titles for text charts might not be desirable in the UI.
- The `dashboard_group` will only have READ permissions for the org, and the dashboard resource will inherit the group's permission. You can edit the `signalfx_dashboard_group` resource to add more permission blocks. Make sure you update the permissions when you create these for the first time since a session token of an administrator is needed to update write permissions of existing groups.
- The script will overwrite any existing files in the directory with same names. If you want to keep the existing files, move them to a different directory before running the script.
- Charts with detector links will have their `program_text` updated to replace the detector_id with terraform resource reference to the detector. The reference is in the format `signalfx_detector.<detector resource name>.id`. The detector resource name is generated by replacing non-alphanumeric characters with `_` in the detector name. If this resource is not found, plan will fail with an error. You can update the detector resource reference in the chart resource to fix the error.
- The script orders the resources alphabetically by their resource type and name in the final terraform file. This is to ensure the order of resources is kind of deterministic and does not change between reruns of import script generating unnecessary diffs in the PRs.
30 changes: 30 additions & 0 deletions import-dashboards-terraform/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module main

go 1.24.0

toolchain go1.24.3

require (
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hcl/v2 v2.24.0
github.com/hashicorp/terraform-exec v0.22.0
github.com/signalfx/signalfx-go v1.52.0
github.com/zclconf/go-cty v1.16.3
)

require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/terraform-json v0.22.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
)

// https://github.com/hashicorp/terraform-exec/pull/446 can be removed after the PR is merged
replace github.com/hashicorp/terraform-exec v0.22.0 => github.com/hrmsk66/terraform-exec v0.21.0
86 changes: 86 additions & 0 deletions import-dashboards-terraform/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0=
github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
github.com/hrmsk66/terraform-exec v0.21.0 h1:k/hnRAZULf6rkzJW7v2fRKAA1wAshyiUv1clw3RklrQ=
github.com/hrmsk66/terraform-exec v0.21.0/go.mod h1:rHqaL9Y7oPlRDZffl2xr/UkSrIhKL2l9G5P81Sza7Ts=
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/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/signalfx/signalfx-go v1.52.0 h1:9NduWTpvGOhsFpnUmk2bSi7YzmeIwvoCVcYck4X530Y=
github.com/signalfx/signalfx-go v1.52.0/go.mod h1:CHt+/W1qd62tXxNqG7ZUB9pEsEAOD6tuvdlyDNIOO1s=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk=
github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
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/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading