├── .github └── workflows │ └── main.yaml ├── .gitignore ├── .goreleaser.yml ├── .vscode └── launch.json ├── LICENSE ├── Makefile ├── README.md ├── docs ├── data-sources │ └── config.md ├── index.md └── resources │ ├── config.md │ ├── config_block.md │ ├── config_block_tree.md │ └── static_host_mapping.md ├── examples ├── provider │ └── provider.tf └── resources │ ├── vyos_config │ ├── import.sh │ └── resource.tf │ ├── vyos_config_block │ ├── import.sh │ └── resource.tf │ ├── vyos_config_block_tree │ ├── import.sh │ └── resource.tf │ └── vyos_static_host_mapping │ └── resource.tf ├── go.mod ├── go.sum ├── main.go ├── terraform-registry-manifest.json ├── tools └── tools.go └── vyos ├── data_source_config.go ├── provider.go ├── resource_config.go ├── resource_config_block.go ├── resource_config_block_tree.go └── resource_static_host_mapping.go /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | goreleaser: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 11 | with: 12 | # Allow goreleaser to access older tag information. 13 | fetch-depth: 0 14 | 15 | - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 16 | with: 17 | go-version-file: 'go.mod' 18 | cache: true 19 | 20 | - name: Import GPG key 21 | id: import_gpg 22 | uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 23 | with: 24 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 25 | passphrase: ${{ secrets.GPG_PASSPHRASE }} 26 | 27 | - name: Run GoReleaser 28 | uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 29 | with: 30 | args: release --clean 31 | env: 32 | # GitHub sets the GITHUB_TOKEN secret automatically. 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | 3 | **/.terraform/* 4 | *.tfstate 5 | *.tfstate.* 6 | .terraform.lock.hcl 7 | terraform-provider-vyos 8 | __debug_bin 9 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | version: 2 4 | before: 5 | hooks: 6 | # this is just an example and not a requirement for provider building/publishing 7 | - go mod tidy 8 | builds: 9 | - env: 10 | # goreleaser does not work with CGO, it could also complicate 11 | # usage by users in CI/CD systems like HCP Terraform where 12 | # they are unable to install libraries. 13 | - CGO_ENABLED=0 14 | mod_timestamp: '{{ .CommitTimestamp }}' 15 | flags: 16 | - -trimpath 17 | ldflags: 18 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 19 | goos: 20 | - freebsd 21 | - windows 22 | - linux 23 | - darwin 24 | goarch: 25 | - amd64 26 | - '386' 27 | - arm 28 | - arm64 29 | ignore: 30 | - goos: darwin 31 | goarch: '386' 32 | binary: '{{ .ProjectName }}_v{{ .Version }}' 33 | archives: 34 | - formats: [zip] 35 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 36 | checksum: 37 | extra_files: 38 | - glob: 'terraform-registry-manifest.json' 39 | name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 40 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 41 | algorithm: sha256 42 | signs: 43 | - artifacts: checksum 44 | args: 45 | # if you are using this in a GitHub action or some other automated pipeline, you 46 | # need to pass the batch flag to indicate its not interactive. 47 | - "--batch" 48 | - "--local-user" 49 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 50 | - "--output" 51 | - "${signature}" 52 | - "--detach-sign" 53 | - "${artifact}" 54 | release: 55 | extra_files: 56 | - glob: 'terraform-registry-manifest.json' 57 | name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 58 | # If you want to manually examine the release before its live, uncomment this line: 59 | # draft: true 60 | changelog: 61 | disable: true -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | 5 | { 6 | "name": "Debug Terraform Provider", 7 | "type": "go", 8 | "request": "launch", 9 | "mode": "debug", 10 | // this assumes your workspace is the root of the repo 11 | "program": "${workspaceFolder}", 12 | "env": {}, 13 | "args": [ 14 | "-debug", 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Jack Foltz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HOSTNAME=github.com 2 | NAMESPACE=foltik 3 | NAME=vyos 4 | BINARY=terraform-provider-${NAME} 5 | VERSION=0.3.1 6 | OS_ARCH=linux_amd64 7 | 8 | default: install 9 | 10 | doc: 11 | go generate 12 | 13 | build: 14 | go build -o ${BINARY} 15 | 16 | release: 17 | GOOS=linux GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_linux_amd64 18 | GOOS=linux GOARCH=arm64 go build -o ./bin/${BINARY}_${VERSION}_linux_arm64 19 | GOOS=linux GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_linux_arm 20 | GOOS=darwin GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_darwin_amd64 21 | GOOS=darwin GOARCH=arm64 go build -o ./bin/${BINARY}_${VERSION}_darwin_arm64 22 | GOOS=windows GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_windows_amd64 23 | 24 | install: build 25 | mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} 26 | cp -a ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} 27 | 28 | test: 29 | go test ./... 30 | 31 | debug: build 32 | ./${BINARY} -debug 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform Provider for VyOS 2 | -------------------------------------------------------------------------------- /docs/data-sources/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "vyos_config Data Source - terraform-provider-vyos" 4 | subcategory: "" 5 | description: |- 6 | 7 | --- 8 | 9 | # vyos_config (Data Source) 10 | 11 | 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - **key** (String) 21 | 22 | ### Optional 23 | 24 | - **id** (String) The ID of this resource. 25 | - **timeouts** (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) 26 | 27 | ### Read-Only 28 | 29 | - **value** (String) 30 | 31 | 32 | ### Nested Schema for `timeouts` 33 | 34 | Optional: 35 | 36 | - **default** (String) 37 | - **read** (String) 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "vyos Provider" 4 | subcategory: "" 5 | description: |- 6 | 7 | --- 8 | 9 | # vyos Provider 10 | 11 | 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | terraform { 17 | required_providers { 18 | vyos = { 19 | version = "0.x.x" 20 | source = "foltik/vyos" 21 | } 22 | } 23 | } 24 | 25 | provider "vyos" { 26 | url = "https://vyos.local" 27 | key = "xxxxxxxxx" 28 | } 29 | ``` 30 | 31 | 32 | ## Schema 33 | 34 | ### Required 35 | 36 | - **key** (String, Sensitive) 37 | - **url** (String) 38 | 39 | ### Optional 40 | 41 | - **cert** (String) 42 | - **save** (Boolean) Save after making changes in Vyos 43 | - **save_file** (String) File to save configuration. Uses config.boot by default. 44 | -------------------------------------------------------------------------------- /docs/resources/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "vyos_config Resource - terraform-provider-vyos" 4 | subcategory: "" 5 | description: |- 6 | This resource manages a single configuration value. This as well as vyosconfigblock can act as a fallback when a dedicated resource does not exist. 7 | --- 8 | 9 | # vyos_config (Resource) 10 | 11 | This resource manages a single configuration value. This as well as vyos_config_block can act as a fallback when a dedicated resource does not exist. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | # Performs "set system host-name vyos" 17 | resource "vyos_config" "hostname" { 18 | key = "system host-name" 19 | value = "vyos" 20 | } 21 | ``` 22 | 23 | 24 | ## Schema 25 | 26 | ### Required 27 | 28 | - **key** (String) Config path separated by spaces. 29 | - **value** (String) Config value. 30 | 31 | ### Optional 32 | 33 | - **timeouts** (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) 34 | 35 | ### Read-Only 36 | 37 | - **id** (String) The resource ID, same as the `key` 38 | 39 | 40 | ### Nested Schema for `timeouts` 41 | 42 | Optional: 43 | 44 | - **create** (String) 45 | - **default** (String) 46 | - **delete** (String) 47 | - **read** (String) 48 | - **update** (String) 49 | 50 | ## Import 51 | 52 | Import is supported using the following syntax: 53 | 54 | ```shell 55 | terraform import vyos_config.hostname "system host-name" 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/resources/config_block.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "vyos_config_block Resource - terraform-provider-vyos" 4 | subcategory: "" 5 | description: |- 6 | This resource is useful when a single command is not enough for a valid config commit. This as well as vyos_config can act as a fallback when a dedicated resource does not exist. 7 | --- 8 | 9 | # vyos_config_block (Resource) 10 | 11 | This resource is useful when a single command is not enough for a valid config commit. This as well as vyos_config can act as a fallback when a dedicated resource does not exist. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | resource "vyos_config_block" "allow_sith_supremacy" { 17 | path = "firewall name Empire-Senate rule 66" 18 | 19 | configs = { 20 | "action" = "accept" 21 | "description" = "For a safe and secure society" 22 | "log" = "disable" 23 | } 24 | } 25 | 26 | resource "vyos_config_block" "allow_sith_supremacy_jedi" { 27 | path = "firewall name Empire-Senate rule 66 destination group" 28 | 29 | configs = { 30 | "port-group" = "Jedi" 31 | } 32 | 33 | depends_on = [vyos_config_block.allow_sith_supremacy] 34 | } 35 | ``` 36 | 37 | 38 | ## Schema 39 | 40 | ### Required 41 | 42 | - **configs** (Map of String) Key/Value map of config parameters. 43 | - **path** (String) Config path seperated by spaces. 44 | 45 | ### Optional 46 | 47 | - **timeouts** (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) 48 | 49 | ### Read-Only 50 | 51 | - **id** (String) The resource ID, same as the `path` 52 | 53 | 54 | ### Nested Schema for `timeouts` 55 | 56 | Optional: 57 | 58 | - **create** (String) 59 | - **default** (String) 60 | - **delete** (String) 61 | - **read** (String) 62 | - **update** (String) 63 | 64 | ## Import 65 | 66 | Import is supported using the following syntax: 67 | 68 | ```shell 69 | terraform import vyos_config_block.allow_sith_supremacy "firewall name Empire-Senate rule 66" 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/resources/config_block_tree.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "vyos_config_block_tree Resource - terraform-provider-vyos" 4 | subcategory: "" 5 | description: |- 6 | This resource is useful when a single command is not enough for a valid config commit and children paths are needed. 7 | --- 8 | 9 | # vyos_config_block_tree (Resource) 10 | 11 | This resource is useful when a single command is not enough for a valid config commit and children paths are needed. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | resource "vyos_config_block" "allow_sith_supremacy" { 17 | path = "firewall name Empire-Senate rule 66" 18 | 19 | configs = { 20 | "action" = "accept" 21 | "description" = "For a safe and secure society" 22 | "log" = "disable" 23 | "destination group port-group": "Jedi" 24 | } 25 | } 26 | 27 | resource "vyos_config_block_tree" "ssh" { 28 | path = "service ssh" 29 | 30 | configs = { 31 | "port" = "22", 32 | "disable-password-authentication" = "", #Keep simple passwords for login via terminal but require key for ssh 33 | "listen-address" = jsonencode(["192.168.2.1", "192.168.63.1"]) # Listen in LAN and management interface 34 | } 35 | } 36 | ``` 37 | 38 | 39 | ## Schema 40 | 41 | ### Required 42 | 43 | - **configs** (Map of String) Key/Value map of config parameters. Value can be a jsonencode list 44 | - **path** (String) Config path seperated by spaces. 45 | 46 | ### Optional 47 | 48 | - **timeouts** (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) 49 | 50 | ### Read-Only 51 | 52 | - **id** (String) The resource ID, same as the `path` 53 | 54 | 55 | ### Nested Schema for `timeouts` 56 | 57 | Optional: 58 | 59 | - **create** (String) 60 | - **default** (String) 61 | - **delete** (String) 62 | - **read** (String) 63 | - **update** (String) 64 | 65 | ## Import 66 | 67 | Import is supported using the following syntax: 68 | 69 | ```shell 70 | terraform import vyos_config_block_tree.allow_sith_supremacy "firewall name Empire-Senate rule 66" 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/resources/static_host_mapping.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "vyos_static_host_mapping Resource - terraform-provider-vyos" 4 | subcategory: "" 5 | description: |- 6 | This resource manages a static host mapping with the given hostname and ipv4 address. 7 | --- 8 | 9 | # vyos_static_host_mapping (Resource) 10 | 11 | This resource manages a static host mapping with the given hostname and ipv4 address. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | # Performs "set system static-host-mapping host-name test.local inet 10.0.0.1" 17 | resource "vyos_static_host_mapping" "mapping" { 18 | host = "test.local" 19 | ip = "10.0.0.1" 20 | } 21 | ``` 22 | 23 | 24 | ## Schema 25 | 26 | ### Required 27 | 28 | - **host** (String) Hostname. 29 | - **ip** (String) IPv4 address. 30 | 31 | ### Optional 32 | 33 | - **id** (String) The ID of this resource. 34 | - **timeouts** (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) 35 | 36 | 37 | ### Nested Schema for `timeouts` 38 | 39 | Optional: 40 | 41 | - **create** (String) 42 | - **default** (String) 43 | - **delete** (String) 44 | - **read** (String) 45 | - **update** (String) 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/provider/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | vyos = { 4 | version = "0.x.x" 5 | source = "foltik/vyos" 6 | } 7 | } 8 | } 9 | 10 | provider "vyos" { 11 | url = "https://vyos.local" 12 | key = "xxxxxxxxx" 13 | } 14 | -------------------------------------------------------------------------------- /examples/resources/vyos_config/import.sh: -------------------------------------------------------------------------------- 1 | terraform import vyos_config.hostname "system host-name" 2 | -------------------------------------------------------------------------------- /examples/resources/vyos_config/resource.tf: -------------------------------------------------------------------------------- 1 | # Performs "set system host-name vyos" 2 | resource "vyos_config" "hostname" { 3 | key = "system host-name" 4 | value = "vyos" 5 | } 6 | -------------------------------------------------------------------------------- /examples/resources/vyos_config_block/import.sh: -------------------------------------------------------------------------------- 1 | terraform import vyos_config_block.allow_sith_supremacy "firewall name Empire-Senate rule 66" 2 | -------------------------------------------------------------------------------- /examples/resources/vyos_config_block/resource.tf: -------------------------------------------------------------------------------- 1 | resource "vyos_config_block" "allow_sith_supremacy" { 2 | path = "firewall name Empire-Senate rule 66" 3 | 4 | configs = { 5 | "action" = "accept" 6 | "description" = "For a safe and secure society" 7 | "log" = "disable" 8 | } 9 | } 10 | 11 | resource "vyos_config_block" "allow_sith_supremacy_jedi" { 12 | path = "firewall name Empire-Senate rule 66 destination group" 13 | 14 | configs = { 15 | "port-group" = "Jedi" 16 | } 17 | 18 | depends_on = [vyos_config_block.allow_sith_supremacy] 19 | } 20 | -------------------------------------------------------------------------------- /examples/resources/vyos_config_block_tree/import.sh: -------------------------------------------------------------------------------- 1 | terraform import vyos_config_block_tree.allow_sith_supremacy "firewall name Empire-Senate rule 66" 2 | -------------------------------------------------------------------------------- /examples/resources/vyos_config_block_tree/resource.tf: -------------------------------------------------------------------------------- 1 | resource "vyos_config_block" "allow_sith_supremacy" { 2 | path = "firewall name Empire-Senate rule 66" 3 | 4 | configs = { 5 | "action" = "accept" 6 | "description" = "For a safe and secure society" 7 | "log" = "disable" 8 | "destination group port-group": "Jedi" 9 | } 10 | } 11 | 12 | resource "vyos_config_block_tree" "ssh" { 13 | path = "service ssh" 14 | 15 | configs = { 16 | "port" = "22", 17 | "disable-password-authentication" = "", #Keep simple passwords for login via terminal but require key for ssh 18 | "listen-address" = jsonencode(["192.168.2.1", "192.168.63.1"]) # Listen in LAN and management interface 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/resources/vyos_static_host_mapping/resource.tf: -------------------------------------------------------------------------------- 1 | # Performs "set system static-host-mapping host-name test.local inet 10.0.0.1" 2 | resource "vyos_static_host_mapping" "mapping" { 3 | host = "test.local" 4 | ip = "10.0.0.1" 5 | } 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/foltik/terraform-provider-vyos 2 | 3 | go 1.23.7 4 | 5 | require ( 6 | github.com/foltik/vyos-client-go v0.4.3-0.20230628033509-5944c2819b30 7 | github.com/hashicorp/terraform-plugin-docs v0.21.0 8 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 9 | ) 10 | 11 | require ( 12 | github.com/BurntSushi/toml v1.2.1 // indirect 13 | github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect 14 | github.com/Masterminds/goutils v1.1.1 // indirect 15 | github.com/Masterminds/semver/v3 v3.2.0 // indirect 16 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 17 | github.com/ProtonMail/go-crypto v1.1.3 // indirect 18 | github.com/agext/levenshtein v1.2.3 // indirect 19 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 20 | github.com/armon/go-radix v1.0.0 // indirect 21 | github.com/bgentry/speakeasy v0.1.0 // indirect 22 | github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect 23 | github.com/cloudflare/circl v1.3.7 // indirect 24 | github.com/fatih/color v1.18.0 // indirect 25 | github.com/go-resty/resty/v2 v2.16.5 // indirect 26 | github.com/golang/protobuf v1.5.4 // indirect 27 | github.com/google/go-cmp v0.7.0 // indirect 28 | github.com/google/uuid v1.6.0 // indirect 29 | github.com/hashicorp/cli v1.1.7 // indirect 30 | github.com/hashicorp/errwrap v1.1.0 // indirect 31 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 32 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 33 | github.com/hashicorp/go-cty v1.5.0 // indirect 34 | github.com/hashicorp/go-hclog v1.6.3 // indirect 35 | github.com/hashicorp/go-multierror v1.1.1 // indirect 36 | github.com/hashicorp/go-plugin v1.6.3 // indirect 37 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 38 | github.com/hashicorp/go-uuid v1.0.3 // indirect 39 | github.com/hashicorp/go-version v1.7.0 // indirect 40 | github.com/hashicorp/hc-install v0.9.1 // indirect 41 | github.com/hashicorp/hcl/v2 v2.23.0 // indirect 42 | github.com/hashicorp/logutils v1.0.0 // indirect 43 | github.com/hashicorp/terraform-exec v0.22.0 // indirect 44 | github.com/hashicorp/terraform-json v0.24.0 // indirect 45 | github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect 46 | github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect 47 | github.com/hashicorp/terraform-registry-address v0.2.5 // indirect 48 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 49 | github.com/hashicorp/yamux v0.1.2 // indirect 50 | github.com/huandu/xstrings v1.3.3 // indirect 51 | github.com/imdario/mergo v0.3.15 // indirect 52 | github.com/mattn/go-colorable v0.1.14 // indirect 53 | github.com/mattn/go-isatty v0.0.20 // indirect 54 | github.com/mattn/go-runewidth v0.0.9 // indirect 55 | github.com/mitchellh/copystructure v1.2.0 // indirect 56 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 57 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 58 | github.com/mitchellh/mapstructure v1.5.0 // indirect 59 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 60 | github.com/oklog/run v1.1.0 // indirect 61 | github.com/posener/complete v1.2.3 // indirect 62 | github.com/shopspring/decimal v1.3.1 // indirect 63 | github.com/spf13/cast v1.5.0 // indirect 64 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 65 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 66 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 67 | github.com/yuin/goldmark v1.7.7 // indirect 68 | github.com/yuin/goldmark-meta v1.1.0 // indirect 69 | github.com/zclconf/go-cty v1.16.2 // indirect 70 | go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect 71 | golang.org/x/crypto v0.37.0 // indirect 72 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect 73 | golang.org/x/mod v0.24.0 // indirect 74 | golang.org/x/net v0.39.0 // indirect 75 | golang.org/x/sync v0.13.0 // indirect 76 | golang.org/x/sys v0.32.0 // indirect 77 | golang.org/x/text v0.24.0 // indirect 78 | golang.org/x/tools v0.32.0 // indirect 79 | google.golang.org/appengine v1.6.8 // indirect 80 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect 81 | google.golang.org/grpc v1.71.1 // indirect 82 | google.golang.org/protobuf v1.36.6 // indirect 83 | gopkg.in/yaml.v2 v2.3.0 // indirect 84 | gopkg.in/yaml.v3 v3.0.1 // indirect 85 | ) 86 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 4 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 5 | github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= 6 | github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= 7 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 8 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 9 | github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= 10 | github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 11 | github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= 12 | github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= 13 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 14 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 15 | github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= 16 | github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= 17 | github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= 18 | github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 19 | github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= 20 | github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= 21 | github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= 22 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 23 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 24 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 25 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 26 | github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= 27 | github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 28 | github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= 29 | github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= 30 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 31 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 32 | github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= 33 | github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 34 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 36 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 38 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 39 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 40 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 41 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 42 | github.com/foltik/vyos-client-go v0.4.3-0.20230628033509-5944c2819b30 h1:FLH0+dQgIBD3Lg6aEfpa1oLMd9drHxiZ3OVxOkRKvRk= 43 | github.com/foltik/vyos-client-go v0.4.3-0.20230628033509-5944c2819b30/go.mod h1:oFBMYnKWwFqhIXHX89oEU0IU3U5fkD9uyTfZkdn4AfM= 44 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 45 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 46 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 47 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 48 | github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= 49 | github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= 50 | github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= 51 | github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= 52 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 53 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 54 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 55 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 56 | github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= 57 | github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= 58 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= 59 | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 60 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 61 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 62 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 63 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 64 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 65 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 66 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 67 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 68 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 69 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 70 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 71 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 72 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 73 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 74 | github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU= 75 | github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU= 76 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 77 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 78 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 79 | github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= 80 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= 81 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 82 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 83 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 84 | github.com/hashicorp/go-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0= 85 | github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM= 86 | github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= 87 | github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 88 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 89 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 90 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 91 | github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= 92 | github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= 93 | github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= 94 | github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 95 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 96 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 97 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 98 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= 99 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 100 | github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ= 101 | github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0= 102 | github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= 103 | github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= 104 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= 105 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 106 | github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64= 107 | github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ= 108 | github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= 109 | github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= 110 | github.com/hashicorp/terraform-plugin-docs v0.21.0 h1:yoyA/Y719z9WdFJAhpUkI1jRbKP/nteVNBaI3hW7iQ8= 111 | github.com/hashicorp/terraform-plugin-docs v0.21.0/go.mod h1:J4Wott1J2XBKZPp/NkQv7LMShJYOcrqhQ2myXBcu64s= 112 | github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= 113 | github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= 114 | github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= 115 | github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= 116 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 h1:WNMsTLkZf/3ydlgsuXePa3jvZFwAJhruxTxP/c1Viuw= 117 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1/go.mod h1:P6o64QS97plG44iFzSM6rAn6VJIC/Sy9a9IkEtl79K4= 118 | github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M= 119 | github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg= 120 | github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= 121 | github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= 122 | github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= 123 | github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= 124 | github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= 125 | github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 126 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 127 | github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= 128 | github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 129 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 130 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 131 | github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= 132 | github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= 133 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 134 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 135 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 136 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 137 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 138 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 139 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 140 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 141 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 142 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 143 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 144 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 145 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 146 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 147 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 148 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 149 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 150 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 151 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 152 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 153 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 154 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 155 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= 156 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 157 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 158 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 159 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 160 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 161 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 162 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 163 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 164 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= 165 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 166 | github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= 167 | github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= 168 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 169 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 170 | github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= 171 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 172 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 173 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 174 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 175 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 176 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 177 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 178 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 179 | github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= 180 | github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= 181 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 182 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 183 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 184 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 185 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 186 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 187 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 188 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 189 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 190 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 191 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 192 | github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= 193 | github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 194 | github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= 195 | github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= 196 | github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 197 | github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 198 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 199 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 200 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 201 | github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU= 202 | github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 203 | github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= 204 | github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= 205 | github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= 206 | github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= 207 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= 208 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= 209 | go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= 210 | go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= 211 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 212 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 213 | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 214 | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 215 | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 216 | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 217 | go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 218 | go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 219 | go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 220 | go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 221 | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 222 | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 223 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 224 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 225 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 226 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 227 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 228 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= 229 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 230 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 231 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 232 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 233 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 234 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 235 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 236 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 237 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 238 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 239 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 240 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 241 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 242 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 243 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 244 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 245 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 246 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 247 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 248 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 249 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 250 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 251 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 252 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 253 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 254 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 255 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 256 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 257 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 258 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 259 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 260 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 261 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 262 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 263 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 264 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 265 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 266 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 267 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 268 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 269 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 270 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 271 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 272 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 273 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 274 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= 275 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 276 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 277 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 278 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 279 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 280 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 281 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA= 282 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 283 | google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= 284 | google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= 285 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 286 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 287 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 288 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 289 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 290 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 291 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 292 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 293 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 294 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 295 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 296 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 297 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 298 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 299 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 6 | 7 | "github.com/foltik/terraform-provider-vyos/vyos" 8 | 9 | "flag" 10 | ) 11 | 12 | // Generate docs 13 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs 14 | 15 | func main() { 16 | var debug bool 17 | 18 | flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") 19 | flag.Parse() 20 | 21 | opts := &plugin.ServeOpts{ 22 | Debug: debug, 23 | ProviderAddr: "registry.terraform.io/foltik/vyos", 24 | ProviderFunc: func() *schema.Provider { 25 | return vyos.Provider() 26 | }, 27 | } 28 | 29 | plugin.Serve(opts) 30 | } 31 | -------------------------------------------------------------------------------- /terraform-registry-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "metadata": { 4 | "protocol_versions": ["6.0"] 5 | } 6 | } -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // Ref: https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md 5 | 6 | package tools 7 | 8 | import ( 9 | // document generation 10 | _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" 11 | ) 12 | -------------------------------------------------------------------------------- /vyos/data_source_config.go: -------------------------------------------------------------------------------- 1 | package vyos 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | func dataSourceConfig() *schema.Resource { 13 | return &schema.Resource{ 14 | ReadContext: dataSourceConfigRead, 15 | Schema: map[string]*schema.Schema{ 16 | "key": { 17 | Type: schema.TypeString, 18 | Required: true, 19 | }, 20 | "value": { 21 | Type: schema.TypeString, 22 | Computed: true, 23 | }, 24 | }, 25 | Timeouts: &schema.ResourceTimeout{ 26 | Read: schema.DefaultTimeout(10 * time.Minute), 27 | Default: schema.DefaultTimeout(10 * time.Minute), 28 | }, 29 | } 30 | } 31 | 32 | func dataSourceConfigRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 33 | p := m.(*ProviderClass) 34 | key := d.Get("key").(string) 35 | 36 | value, err := p.ShowCached(ctx, key) 37 | if err != nil { 38 | return diag.FromErr(err) 39 | } 40 | 41 | switch value := value.(type) { 42 | case string: 43 | if err := d.Set("value", value); err != nil { 44 | return diag.FromErr(err) 45 | } 46 | default: 47 | return diag.Errorf("Configuration at '%s' is not a string: %s.", key, value) 48 | } 49 | 50 | d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) 51 | return diag.Diagnostics{} 52 | } 53 | -------------------------------------------------------------------------------- /vyos/provider.go: -------------------------------------------------------------------------------- 1 | package vyos 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "errors" 7 | "net/http" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/foltik/vyos-client-go/client" 13 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 14 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 15 | ) 16 | 17 | func Provider() *schema.Provider { 18 | return &schema.Provider{ 19 | Schema: map[string]*schema.Schema{ 20 | "url": { 21 | Type: schema.TypeString, 22 | Required: true, 23 | }, 24 | "key": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | Sensitive: true, 28 | DefaultFunc: schema.EnvDefaultFunc("VYOS_KEY", nil), 29 | }, 30 | "cert": { 31 | Type: schema.TypeString, 32 | Optional: true, 33 | }, 34 | "save": { 35 | Type: schema.TypeBool, 36 | Optional: true, 37 | Default: true, 38 | Description: "Save after making changes in Vyos", 39 | }, 40 | "save_file": { 41 | Type: schema.TypeString, 42 | Optional: true, 43 | Description: "File to save configuration. Uses config.boot by default.", 44 | }, 45 | "cache": { 46 | Type: schema.TypeBool, 47 | Optional: true, 48 | Default: true, 49 | Description: "Use cache for read operations", 50 | }, 51 | }, 52 | ResourcesMap: map[string]*schema.Resource{ 53 | "vyos_config": resourceConfig(), 54 | "vyos_config_block": resourceConfigBlock(), 55 | "vyos_config_block_tree": resourceConfigBlockTree(), 56 | "vyos_static_host_mapping": resourceStaticHostMapping(), 57 | }, 58 | DataSourcesMap: map[string]*schema.Resource{ 59 | "vyos_config": dataSourceConfig(), 60 | }, 61 | ConfigureContextFunc: providerConfigure, 62 | } 63 | } 64 | 65 | type ProviderClass struct { 66 | schema *schema.ResourceData 67 | client *client.Client 68 | 69 | _showCacheMutex *sync.Mutex 70 | _showCache *map[string]any 71 | } 72 | 73 | func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { 74 | url := d.Get("url").(string) 75 | key := d.Get("key").(string) 76 | 77 | cert := d.Get("cert").(string) 78 | c := &client.Client{} 79 | 80 | if cert != "" { 81 | return nil, diag.Errorf("TODO: Use trusted self signed certificate") 82 | } else { 83 | // Just allow self signed certificates if a trusted cert isn't specified 84 | tr := http.DefaultTransport.(*http.Transport).Clone() 85 | tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 86 | cc := &http.Client{Transport: tr, Timeout: 10 * time.Minute} 87 | c = client.NewWithClient(cc, url, key) 88 | } 89 | 90 | return &ProviderClass{d, c, &sync.Mutex{}, nil}, diag.Diagnostics{} 91 | } 92 | 93 | func (p *ProviderClass) conditionalSave(ctx context.Context) { 94 | save := p.schema.Get("save").(bool) 95 | save_file := p.schema.Get("save_file").(string) 96 | 97 | if save { 98 | if save_file == "" { 99 | p.client.Config.Save(ctx) 100 | } else { 101 | p.client.Config.SaveFile(ctx, save_file) 102 | } 103 | } 104 | } 105 | 106 | func (p *ProviderClass) ShowCached(ctx context.Context, path string) (any, error) { 107 | cache := p.schema.Get("cache").(bool) 108 | 109 | if !cache { 110 | return p.client.Config.Show(ctx, path) 111 | } 112 | 113 | p._showCacheMutex.Lock() 114 | if p._showCache == nil { 115 | c := *p.client 116 | showCache, err := c.Config.Show(ctx, "") 117 | if err != nil { 118 | return showCache, err 119 | } 120 | switch value := showCache.(type) { 121 | case map[string]any: 122 | p._showCache = &value 123 | default: 124 | return nil, errors.New("Configuration is not a map") 125 | } 126 | } 127 | p._showCacheMutex.Unlock() 128 | 129 | var val any = *p._showCache 130 | for _, component := range strings.Split(path, " ") { 131 | obj, ok := val.(map[string]any)[component] 132 | if !ok { 133 | return nil, nil 134 | } 135 | val = obj 136 | } 137 | return val, nil 138 | 139 | } 140 | -------------------------------------------------------------------------------- /vyos/resource_config.go: -------------------------------------------------------------------------------- 1 | package vyos 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | func resourceConfig() *schema.Resource { 13 | return &schema.Resource{ 14 | Description: "This resource manages a single configuration value. This as well as vyos_config_block can act as a fallback when a dedicated resource does not exist.", 15 | CreateContext: resourceConfigCreate, 16 | ReadContext: resourceConfigRead, 17 | UpdateContext: resourceConfigUpdate, 18 | DeleteContext: resourceConfigDelete, 19 | Importer: &schema.ResourceImporter{ 20 | StateContext: schema.ImportStatePassthroughContext, 21 | }, 22 | Schema: map[string]*schema.Schema{ 23 | "id": { 24 | Description: "The resource ID, same as the `key`", 25 | Type: schema.TypeString, 26 | Computed: true, 27 | }, 28 | "key": { 29 | Description: "Config path separated by spaces.", 30 | Type: schema.TypeString, 31 | Required: true, 32 | }, 33 | "value": { 34 | Description: "Config value.", 35 | Type: schema.TypeString, 36 | Required: true, 37 | }, 38 | }, 39 | Timeouts: &schema.ResourceTimeout{ 40 | Create: schema.DefaultTimeout(10 * time.Minute), 41 | Read: schema.DefaultTimeout(10 * time.Minute), 42 | Update: schema.DefaultTimeout(10 * time.Minute), 43 | Delete: schema.DefaultTimeout(10 * time.Minute), 44 | Default: schema.DefaultTimeout(10 * time.Minute), 45 | }, 46 | } 47 | } 48 | 49 | func resourceConfigCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 50 | p := m.(*ProviderClass) 51 | c := *p.client 52 | key, value := d.Get("key").(string), d.Get("value").(string) 53 | 54 | var diags diag.Diagnostics 55 | 56 | // Check if config already exists 57 | val, err := p.ShowCached(ctx, key) 58 | if err != nil { 59 | return diag.FromErr(err) 60 | } 61 | // Dont care about sub config blocks 62 | if val != nil { 63 | return diag.Errorf("Configuration '%s' already exists with value '%s' set, try a resource import instead.", key, val) 64 | } 65 | 66 | err = c.Config.Set(ctx, key, value) 67 | if err != nil { 68 | return diag.FromErr(err) 69 | } 70 | 71 | d.SetId(key) 72 | p.conditionalSave(ctx) 73 | return diags 74 | } 75 | 76 | func resourceConfigRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 77 | p := m.(*ProviderClass) 78 | key := d.Id() 79 | 80 | // Convert old unix timestamp style ID to key path for existing resources to support importing 81 | if _, err := strconv.Atoi(key); err == nil { 82 | key = d.Get("key").(string) 83 | d.SetId(key) 84 | } 85 | 86 | // Easiest way to allow ImportStatePassthroughContext to work is to set the path 87 | if d.Get("key") == "" { 88 | if err := d.Set("key", key); err != nil { 89 | return diag.FromErr(err) 90 | } 91 | } 92 | 93 | value, err := p.ShowCached(ctx, key) 94 | if err != nil { 95 | return diag.FromErr(err) 96 | } 97 | 98 | if err := d.Set("value", value); err != nil { 99 | return diag.FromErr(err) 100 | } 101 | 102 | return diag.Diagnostics{} 103 | } 104 | 105 | func resourceConfigUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 106 | p := m.(*ProviderClass) 107 | c := *p.client 108 | key, value := d.Get("key").(string), d.Get("value").(string) 109 | 110 | err := c.Config.Set(ctx, key, value) 111 | if err != nil { 112 | return diag.FromErr(err) 113 | } 114 | 115 | p.conditionalSave(ctx) 116 | return diag.Diagnostics{} 117 | } 118 | 119 | func resourceConfigDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 120 | p := m.(*ProviderClass) 121 | c := *p.client 122 | key := d.Get("key").(string) 123 | 124 | err := c.Config.Delete(ctx, key) 125 | if err != nil { 126 | return diag.FromErr(err) 127 | } 128 | 129 | p.conditionalSave(ctx) 130 | return diag.Diagnostics{} 131 | } 132 | -------------------------------------------------------------------------------- /vyos/resource_config_block.go: -------------------------------------------------------------------------------- 1 | package vyos 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 11 | ) 12 | 13 | func resourceConfigBlock() *schema.Resource { 14 | return &schema.Resource{ 15 | Description: "This resource is useful when a single command is not enough for a valid config commit. This as well as vyos_config can act as a fallback when a dedicated resource does not exist.", 16 | CreateContext: resourceConfigBlockCreate, 17 | ReadContext: resourceConfigBlockRead, 18 | UpdateContext: resourceConfigBlockUpdate, 19 | DeleteContext: resourceConfigBlockDelete, 20 | Importer: &schema.ResourceImporter{ 21 | StateContext: schema.ImportStatePassthroughContext, 22 | }, 23 | Schema: map[string]*schema.Schema{ 24 | "id": { 25 | Description: "The resource ID, same as the `path`", 26 | Type: schema.TypeString, 27 | Computed: true, 28 | }, 29 | "path": { 30 | Description: "Config path seperated by spaces.", 31 | Type: schema.TypeString, 32 | Required: true, 33 | ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), 34 | ForceNew: true, 35 | }, 36 | "configs": { 37 | Description: "Key/Value map of config parameters.", 38 | Type: schema.TypeMap, 39 | Elem: &schema.Schema{ 40 | Type: schema.TypeString, 41 | }, 42 | Required: true, 43 | ValidateDiagFunc: validation.MapKeyMatch(regexp.MustCompile("^[^ ]+$"), "Config keys can not contain whitespace"), 44 | }, 45 | }, 46 | Timeouts: &schema.ResourceTimeout{ 47 | Create: schema.DefaultTimeout(10 * time.Minute), 48 | Read: schema.DefaultTimeout(10 * time.Minute), 49 | Update: schema.DefaultTimeout(10 * time.Minute), 50 | Delete: schema.DefaultTimeout(10 * time.Minute), 51 | Default: schema.DefaultTimeout(10 * time.Minute), 52 | }, 53 | } 54 | } 55 | 56 | func resourceConfigBlockCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 57 | var diags diag.Diagnostics 58 | 59 | p := m.(*ProviderClass) 60 | client := *p.client 61 | path := d.Get("path").(string) 62 | 63 | // Check if config already exists 64 | configs, err := p.ShowCached(ctx, path) 65 | if err != nil { 66 | return diag.FromErr(err) 67 | } 68 | 69 | // Dont care about sub config blocks 70 | if configs != nil { 71 | return diag.Errorf("Configuration '%s' already exists with value '%s' set, try a resource import instead.", path, configs) 72 | } 73 | 74 | configs = d.Get("configs").(map[string]interface{}) 75 | 76 | err = client.Config.Set(ctx, path, configs) 77 | if err != nil { 78 | return diag.FromErr(err) 79 | } 80 | 81 | d.SetId(path) 82 | p.conditionalSave(ctx) 83 | return diags 84 | } 85 | 86 | func resourceConfigBlockRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 87 | var diags diag.Diagnostics 88 | 89 | p := m.(*ProviderClass) 90 | path := d.Id() 91 | 92 | configs, err := p.ShowCached(ctx, path) 93 | if err != nil { 94 | return diag.FromErr(err) 95 | } 96 | 97 | switch value := configs.(type) { 98 | case map[string]any: 99 | if err := d.Set("configs", value); err != nil { 100 | return diag.FromErr(err) 101 | } 102 | return diags 103 | default: 104 | return diag.Errorf("Configuration at '%s' is not a string: %s.", path, value) 105 | } 106 | 107 | // // Remove child blocks of config 108 | // for attr, val := range configs { 109 | // switch val.(type) { 110 | // default: 111 | // delete(configs, attr) 112 | // case string: 113 | // continue 114 | // case int: 115 | // continue 116 | // } 117 | // } 118 | 119 | } 120 | 121 | func resourceConfigBlockUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 122 | var diags diag.Diagnostics 123 | 124 | p := m.(*ProviderClass) 125 | c := *p.client 126 | 127 | path := d.Get("path").(string) 128 | o, n := d.GetChange("configs") 129 | old_configs := o.(map[string]interface{}) 130 | new_configs := n.(map[string]interface{}) 131 | 132 | deleted_attrs := []string{} 133 | 134 | for old_attr := range old_configs { 135 | value, ok := new_configs[old_attr] 136 | _ = value 137 | if !ok { 138 | deleted_attrs = append(deleted_attrs, old_attr) 139 | } 140 | } 141 | 142 | errDel := c.Config.Delete(ctx, path, deleted_attrs) 143 | if errDel != nil { 144 | return diag.FromErr(errDel) 145 | } 146 | 147 | errSet := c.Config.Set(ctx, path, new_configs) 148 | if errSet != nil { 149 | return diag.FromErr(errSet) 150 | } 151 | 152 | p.conditionalSave(ctx) 153 | return diags 154 | } 155 | 156 | func resourceConfigBlockDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 157 | var diags diag.Diagnostics 158 | 159 | p := m.(*ProviderClass) 160 | c := *p.client 161 | path := d.Get("path").(string) 162 | 163 | err := c.Config.Delete(ctx, path) 164 | if err != nil { 165 | return diag.FromErr(err) 166 | } 167 | 168 | p.conditionalSave(ctx) 169 | return diags 170 | } 171 | -------------------------------------------------------------------------------- /vyos/resource_config_block_tree.go: -------------------------------------------------------------------------------- 1 | package vyos 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "reflect" 7 | "sort" 8 | "strings" 9 | "time" 10 | 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 13 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 14 | 15 | "github.com/foltik/vyos-client-go/client" 16 | ) 17 | 18 | func resourceConfigBlockTree() *schema.Resource { 19 | return &schema.Resource{ 20 | Description: "This resource is useful when a single command is not enough for a valid config commit and children paths are needed.", 21 | CreateContext: resourceConfigBlockTreeCreate, 22 | ReadContext: resourceConfigBlockTreeRead, 23 | UpdateContext: resourceConfigBlockTreeUpdate, 24 | DeleteContext: resourceConfigBlockTreeDelete, 25 | Importer: &schema.ResourceImporter{ 26 | StateContext: schema.ImportStatePassthroughContext, 27 | }, 28 | Schema: map[string]*schema.Schema{ 29 | "id": { 30 | Description: "The resource ID, same as the `path`", 31 | Type: schema.TypeString, 32 | Computed: true, 33 | }, 34 | "path": { 35 | Description: "Config path seperated by spaces.", 36 | Type: schema.TypeString, 37 | Required: true, 38 | ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), 39 | ForceNew: true, 40 | }, 41 | "configs": { 42 | Description: "Key/Value map of config parameters. Value can be a jsonencode list", 43 | Type: schema.TypeMap, 44 | Elem: &schema.Schema{ 45 | Type: schema.TypeString, 46 | }, 47 | Required: true, 48 | DiffSuppressFunc: configDiffSuppressFunc, 49 | }, 50 | }, 51 | Timeouts: &schema.ResourceTimeout{ 52 | Create: schema.DefaultTimeout(10 * time.Minute), 53 | Read: schema.DefaultTimeout(10 * time.Minute), 54 | Update: schema.DefaultTimeout(10 * time.Minute), 55 | Delete: schema.DefaultTimeout(10 * time.Minute), 56 | Default: schema.DefaultTimeout(10 * time.Minute), 57 | }, 58 | } 59 | } 60 | 61 | func configDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool { 62 | 63 | multivalueOld := []string{} 64 | err := json.Unmarshal([]byte(old), &multivalueOld) 65 | if err != nil { 66 | return false 67 | } 68 | sort.Strings(multivalueOld) 69 | 70 | multivalueNew := []string{} 71 | err = json.Unmarshal([]byte(new), &multivalueNew) 72 | if err != nil { 73 | return false 74 | } 75 | sort.Strings(multivalueNew) 76 | 77 | return reflect.DeepEqual(multivalueOld, multivalueNew) 78 | } 79 | 80 | // Covert configs to a set of vyos client commands. 81 | // If expand_slice is set, then list values (json encoded) are expanded in multiple vyos client commands 82 | // If expand_slice is not set then the values in the map might contain slices 83 | func getCommandsForConfig(config interface{}, expand_slice bool) (commands map[string]any) { 84 | 85 | commands = map[string]interface{}{} 86 | for key, value := range config.(map[string]interface{}) { 87 | 88 | // Try to decode the string as json list 89 | value := value.(string) 90 | multivalue := []string{} 91 | err := json.Unmarshal([]byte(value), &multivalue) 92 | if err == nil { 93 | if expand_slice { 94 | for _, subvalue := range multivalue { 95 | commands[key+" "+subvalue] = "" 96 | } 97 | } else { 98 | commands[key] = multivalue 99 | } 100 | } else { 101 | // Could not decode json string - assume single value string 102 | if expand_slice { 103 | commands[key] = value 104 | } else { 105 | commands[key] = []string{value} 106 | } 107 | } 108 | } 109 | return 110 | } 111 | 112 | func resourceConfigBlockTreeCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 113 | var diags diag.Diagnostics 114 | 115 | p := m.(*ProviderClass) 116 | client := *p.client 117 | path := d.Get("path").(string) 118 | 119 | // Get commands needed to create resource in Vyos 120 | commands := getCommandsForConfig(d.Get("configs"), true) 121 | 122 | err := client.Config.Set(ctx, path, commands) 123 | if err != nil { 124 | return diag.FromErr(err) 125 | } 126 | 127 | d.SetId(path) 128 | p.conditionalSave(ctx) 129 | return diags 130 | } 131 | 132 | func resourceConfigBlockTreeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 133 | var diags diag.Diagnostics 134 | 135 | p := m.(*ProviderClass) 136 | //c := *p.client 137 | path := d.Id() 138 | 139 | configsTree, err := p.ShowCached(ctx, path) 140 | if err != nil { 141 | return diag.FromErr(err) 142 | } 143 | 144 | flat, err := client.Flatten(configsTree) 145 | if err != nil { 146 | return diag.FromErr(err) 147 | } 148 | 149 | // Convert Vyos commands to Terraform schema 150 | configs := map[string]interface{}{} 151 | for _, config := range flat { 152 | key := config[0] 153 | value := config[1] 154 | existing_value, ok := configs[key] 155 | if ok { 156 | // This is command with multiple values 157 | switch existing_value := existing_value.(type) { 158 | case string: 159 | // Second value for command found - convert to slice 160 | configs[key] = []string{existing_value, value} 161 | case []string: 162 | // N value for command found - append to slice 163 | configs[key] = append(existing_value, value) 164 | } 165 | } else { 166 | configs[key] = value 167 | } 168 | } 169 | 170 | // If there are slices then covert them to json strings 171 | for key, value := range configs { 172 | switch value := value.(type) { 173 | case []string: 174 | jsonBytes, _ := json.Marshal(value) 175 | configs[key] = string(jsonBytes) 176 | } 177 | } 178 | 179 | // Easiest way to allow ImportStatePassthroughContext to work is to set the path 180 | if d.Get("path") == "" { 181 | if err := d.Set("path", path); err != nil { 182 | return diag.FromErr(err) 183 | } 184 | } 185 | 186 | if err := d.Set("configs", configs); err != nil { 187 | return diag.FromErr(err) 188 | } 189 | 190 | return diags 191 | } 192 | 193 | func resourceConfigBlockTreeUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 194 | var diags diag.Diagnostics 195 | 196 | p := m.(*ProviderClass) 197 | c := *p.client 198 | 199 | path := d.Get("path").(string) 200 | o, n := d.GetChange("configs") 201 | old_configs := o.(map[string]interface{}) 202 | new_configs := n.(map[string]interface{}) 203 | 204 | // Get commands needed to create old a new config 205 | old_comands := getCommandsForConfig(old_configs, false) 206 | new_comands := getCommandsForConfig(new_configs, false) 207 | 208 | // NOTE: it is important to apply new settings before deleting to 209 | // avoid errors. This is because delete and set are 2 210 | // different API calls and this might result in invalid 211 | // intermediary configs if we delete first. 212 | 213 | // Calculate new commands (new config minus old config) 214 | set_commands := map[string]interface{}{} 215 | for command, new_value := range new_comands { 216 | old_value, ok := old_comands[command] 217 | if !ok || !reflect.DeepEqual(new_value, old_value) { 218 | switch new_value := new_value.(type) { 219 | case []string: 220 | // List value - multiple subcommands 221 | for _, value := range new_value { 222 | set_commands[command+" "+value] = "" 223 | } 224 | case string: 225 | set_commands[command] = new_value 226 | } 227 | } 228 | } 229 | if len(new_comands) > 0 { 230 | errSet := c.Config.Set(ctx, path, new_comands) 231 | if errSet != nil { 232 | return diag.FromErr(errSet) 233 | } 234 | } 235 | 236 | // Calculate delete commands (old config minus new config) 237 | delete_commands := map[string]interface{}{} 238 | for command, old_value := range old_comands { 239 | new_value, ok := new_comands[command] 240 | if !ok { 241 | // Not found in new config - delete commpletly 242 | if len(command) > len(path) { 243 | // Do not delete path 244 | delete_commands[command] = "" 245 | } 246 | } else { 247 | // Compare old and new values for this command 248 | for _, old_value_part := range old_value.([]string) { 249 | found := false 250 | for _, new_value_part := range new_value.([]string) { 251 | if old_value_part == new_value_part { 252 | found = true 253 | break 254 | } 255 | } 256 | // Only delete if old value not in new config AND this 257 | // command is bellow the resource path 258 | if !found && len(command) > len(path) { 259 | delete_commands[command] = old_value_part 260 | } 261 | } 262 | } 263 | } 264 | // Remove orphan nodes as well 265 | // An orphan is a node that does not have any entries in the new config 266 | // 267 | // Example: "system static-host-mapping host-name foo inet" => 1.2.3.4" 268 | // This will fail if we delete only "system static-host-mapping host-name foo inet" 269 | // and do not delete "system static-host-mapping host-name foo" as well 270 | for key, _ := range delete_commands { 271 | key_parts := strings.Split(key, " ") 272 | parent := "" 273 | out: 274 | for _, key_part := range key_parts { 275 | if len(parent) > 0 { 276 | parent += " " 277 | } 278 | parent += key_part 279 | found := false 280 | for key, _ := range new_comands { 281 | if strings.Contains(key, parent) { 282 | // parent still exists in new config - keep parent 283 | found = true 284 | break 285 | } 286 | } 287 | if !found && len(parent) > len(path) { 288 | // Delete parent if not done already 289 | if _, ok := delete_commands[parent]; !ok { 290 | delete_commands[parent] = "" 291 | } 292 | break out 293 | } 294 | } 295 | } 296 | if len(delete_commands) > 0 { 297 | errDel := c.Config.Delete(ctx, path, delete_commands) 298 | if errDel != nil { 299 | return diag.FromErr(errDel) 300 | } 301 | } 302 | 303 | p.conditionalSave(ctx) 304 | return diags 305 | } 306 | 307 | func resourceConfigBlockTreeDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 308 | var diags diag.Diagnostics 309 | 310 | p := m.(*ProviderClass) 311 | c := *p.client 312 | path := d.Get("path").(string) 313 | 314 | err := c.Config.Delete(ctx, path) 315 | if err != nil { 316 | return diag.FromErr(err) 317 | } 318 | 319 | p.conditionalSave(ctx) 320 | return diags 321 | } 322 | -------------------------------------------------------------------------------- /vyos/resource_static_host_mapping.go: -------------------------------------------------------------------------------- 1 | package vyos 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | func resourceStaticHostMapping() *schema.Resource { 14 | return &schema.Resource{ 15 | Description: "This resource manages a static host mapping with the given hostname and ipv4 address.", 16 | CreateContext: resourceStaticHostMappingCreate, 17 | ReadContext: resourceStaticHostMappingRead, 18 | UpdateContext: resourceStaticHostMappingUpdate, 19 | DeleteContext: resourceStaticHostMappingDelete, 20 | Schema: map[string]*schema.Schema{ 21 | "host": { 22 | Description: "Hostname.", 23 | Type: schema.TypeString, 24 | Required: true, 25 | }, 26 | "ip": { 27 | Description: "IPv4 address.", 28 | Type: schema.TypeString, 29 | Required: true, 30 | }, 31 | }, 32 | Timeouts: &schema.ResourceTimeout{ 33 | Create: schema.DefaultTimeout(10 * time.Minute), 34 | Read: schema.DefaultTimeout(10 * time.Minute), 35 | Update: schema.DefaultTimeout(10 * time.Minute), 36 | Delete: schema.DefaultTimeout(10 * time.Minute), 37 | Default: schema.DefaultTimeout(10 * time.Minute), 38 | }, 39 | } 40 | } 41 | 42 | func resourceStaticHostMappingCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 43 | p := m.(*ProviderClass) 44 | c := *p.client 45 | host, ip := d.Get("host").(string), d.Get("ip").(string) 46 | 47 | path := fmt.Sprintf("system static-host-mapping host-name %s inet", host) 48 | err := c.Config.Set(ctx, path, ip) 49 | if err != nil { 50 | return diag.FromErr(err) 51 | } 52 | 53 | d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) 54 | p.conditionalSave(ctx) 55 | return diag.Diagnostics{} 56 | } 57 | 58 | func resourceStaticHostMappingRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 59 | p := m.(*ProviderClass) 60 | host := d.Get("host").(string) 61 | 62 | path := fmt.Sprintf("system static-host-mapping host-name %s inet", host) 63 | ip, err := p.ShowCached(ctx, path) 64 | if err != nil { 65 | return diag.FromErr(err) 66 | } 67 | 68 | if err := d.Set("ip", ip); err != nil { 69 | return diag.FromErr(err) 70 | } 71 | 72 | return diag.Diagnostics{} 73 | } 74 | 75 | func resourceStaticHostMappingUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 76 | p := m.(*ProviderClass) 77 | c := *p.client 78 | host, ip := d.Get("host").(string), d.Get("ip").(string) 79 | 80 | // If the hostname changes, so does the configuration path, 81 | // so we need to delete the old mapping. 82 | if d.HasChange("host") { 83 | old, _ := d.GetChange("host") 84 | path := fmt.Sprintf("system static-host-mapping host-name %s", old) 85 | err := c.Config.Delete(ctx, path) 86 | if err != nil { 87 | return diag.FromErr(err) 88 | } 89 | } 90 | 91 | path := fmt.Sprintf("system static-host-mapping host-name %s inet", host) 92 | err := c.Config.Set(ctx, path, ip) 93 | if err != nil { 94 | return diag.FromErr(err) 95 | } 96 | 97 | p.conditionalSave(ctx) 98 | return diag.Diagnostics{} 99 | } 100 | 101 | func resourceStaticHostMappingDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 102 | p := m.(*ProviderClass) 103 | c := *p.client 104 | host := d.Get("host").(string) 105 | 106 | path := fmt.Sprintf("system static-host-mapping host-name %s", host) 107 | err := c.Config.Delete(ctx, path) 108 | if err != nil { 109 | return diag.FromErr(err) 110 | } 111 | 112 | p.conditionalSave(ctx) 113 | return diag.Diagnostics{} 114 | } 115 | --------------------------------------------------------------------------------