From c7cd61a2d3aea273df40e86532fa575c0dd95b98 Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Sun, 10 Aug 2025 20:15:00 -0400 Subject: [PATCH 01/10] chore(docs): cleanup and update `clone-vm` example (#2094) Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- docs/guides/clone-vm.md | 20 +++++++++++--------- examples/guides/clone-vm/clone.tf | 2 +- examples/guides/clone-vm/cloud-config.tf | 2 +- examples/guides/clone-vm/template.tf | 8 ++++---- examples/guides/clone-vm/variables.tf | 12 ++++++++++++ templates/guides/clone-vm.md.tmpl | 10 ++++++---- 6 files changed, 35 insertions(+), 19 deletions(-) diff --git a/docs/guides/clone-vm.md b/docs/guides/clone-vm.md index 813850f8..abf7bcaf 100644 --- a/docs/guides/clone-vm.md +++ b/docs/guides/clone-vm.md @@ -3,21 +3,21 @@ layout: page page_title: "Clone a VM" subcategory: Guides description: |- - This guide explains how to create a VM template and then clone it to another VM. + This guide explains how to create a VM template and clone it to a new VM. --- # Clone a VM ## Create a VM template -VM templates in Proxmox provide an efficient way to create multiple identical VMs. Templates act as a base image that can be cloned to create new VMs, ensuring consistency and reducing the time needed to provision new instances. When a VM is created as a template, it is read-only and can't be started, but can be cloned multiple times to create new VMs. +VM templates in Proxmox provide an efficient way to create multiple identical VMs. Templates act as a base image that can be cloned to create new VMs, ensuring consistency and reducing the time needed to provision new instances. When a VM is created as a template, it is read-only and cannot be started, but can be cloned multiple times to create new VMs. -You can create a template directly in Proxmox by setting the `template` attribute to `true` when creating the VM resource: +You can create a template with Terraform by setting the `template` attribute to `true` when creating the VM resource: ```terraform resource "proxmox_virtual_environment_vm" "ubuntu_template" { name = "ubuntu-template" - node_name = "pve" + node_name = var.virtual_environment_node_name template = true started = false @@ -35,12 +35,12 @@ resource "proxmox_virtual_environment_vm" "ubuntu_template" { } efi_disk { - datastore_id = "local" + datastore_id = var.datastore_id type = "4m" } disk { - datastore_id = "local-lvm" + datastore_id = var.datastore_id file_id = proxmox_virtual_environment_download_file.ubuntu_cloud_image.id interface = "virtio0" iothread = true @@ -67,18 +67,18 @@ resource "proxmox_virtual_environment_vm" "ubuntu_template" { resource "proxmox_virtual_environment_download_file" "ubuntu_cloud_image" { content_type = "iso" datastore_id = "local" - node_name = "pve" + node_name = var.virtual_environment_node_name url = "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img" } ``` -Once you have a template, you can clone it to create new VMs. The cloned VMs will inherit all the configuration from the template but can be customized further as needed. +Once you have a template, you can clone it to create new VMs. The cloned VMs will inherit all configuration from the template but can be customized further as needed. ```terraform resource "proxmox_virtual_environment_vm" "ubuntu_clone" { name = "ubuntu-clone" - node_name = "pve" + node_name = var.virtual_environment_node_name clone { vm_id = proxmox_virtual_environment_vm.ubuntu_template.id @@ -113,3 +113,5 @@ output "vm_ipv4_address" { value = proxmox_virtual_environment_vm.ubuntu_clone.ipv4_addresses[1][0] } ``` + +Full example is available in the [examples/guides/clone-vm](https://github.com/bpg/terraform-provider-proxmox/tree/main/examples/guides/clone-vm) directory. diff --git a/examples/guides/clone-vm/clone.tf b/examples/guides/clone-vm/clone.tf index e881eb20..4f3f14af 100644 --- a/examples/guides/clone-vm/clone.tf +++ b/examples/guides/clone-vm/clone.tf @@ -1,6 +1,6 @@ resource "proxmox_virtual_environment_vm" "ubuntu_clone" { name = "ubuntu-clone" - node_name = "pve" + node_name = var.virtual_environment_node_name clone { vm_id = proxmox_virtual_environment_vm.ubuntu_template.id diff --git a/examples/guides/clone-vm/cloud-config.tf b/examples/guides/clone-vm/cloud-config.tf index 05f57509..053f617d 100644 --- a/examples/guides/clone-vm/cloud-config.tf +++ b/examples/guides/clone-vm/cloud-config.tf @@ -5,7 +5,7 @@ data "local_file" "ssh_public_key" { resource "proxmox_virtual_environment_file" "user_data_cloud_config" { content_type = "snippets" datastore_id = "local" - node_name = "pve" + node_name = var.virtual_environment_node_name source_raw { data = <<-EOF diff --git a/examples/guides/clone-vm/template.tf b/examples/guides/clone-vm/template.tf index c96dd86b..e8406bbb 100644 --- a/examples/guides/clone-vm/template.tf +++ b/examples/guides/clone-vm/template.tf @@ -1,6 +1,6 @@ resource "proxmox_virtual_environment_vm" "ubuntu_template" { name = "ubuntu-template" - node_name = "pve" + node_name = var.virtual_environment_node_name template = true started = false @@ -18,12 +18,12 @@ resource "proxmox_virtual_environment_vm" "ubuntu_template" { } efi_disk { - datastore_id = "local" + datastore_id = var.datastore_id type = "4m" } disk { - datastore_id = "local-lvm" + datastore_id = var.datastore_id file_id = proxmox_virtual_environment_download_file.ubuntu_cloud_image.id interface = "virtio0" iothread = true @@ -50,7 +50,7 @@ resource "proxmox_virtual_environment_vm" "ubuntu_template" { resource "proxmox_virtual_environment_download_file" "ubuntu_cloud_image" { content_type = "iso" datastore_id = "local" - node_name = "pve" + node_name = var.virtual_environment_node_name url = "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img" } diff --git a/examples/guides/clone-vm/variables.tf b/examples/guides/clone-vm/variables.tf index 62017de9..01377701 100644 --- a/examples/guides/clone-vm/variables.tf +++ b/examples/guides/clone-vm/variables.tf @@ -8,3 +8,15 @@ variable "virtual_environment_token" { description = "The token for the Proxmox Virtual Environment API" sensitive = true } + +variable "virtual_environment_node_name" { + type = string + description = "The node name for the Proxmox Virtual Environment API" + default = "pve" +} + +variable "datastore_id" { + type = string + description = "Datastore for VM disks" + default = "local-lvm" +} diff --git a/templates/guides/clone-vm.md.tmpl b/templates/guides/clone-vm.md.tmpl index 58047205..e739cf19 100644 --- a/templates/guides/clone-vm.md.tmpl +++ b/templates/guides/clone-vm.md.tmpl @@ -3,19 +3,21 @@ layout: page page_title: "Clone a VM" subcategory: Guides description: |- - This guide explains how to create a VM template and then clone it to another VM. + This guide explains how to create a VM template and clone it to a new VM. --- # Clone a VM ## Create a VM template -VM templates in Proxmox provide an efficient way to create multiple identical VMs. Templates act as a base image that can be cloned to create new VMs, ensuring consistency and reducing the time needed to provision new instances. When a VM is created as a template, it is read-only and can't be started, but can be cloned multiple times to create new VMs. +VM templates in Proxmox provide an efficient way to create multiple identical VMs. Templates act as a base image that can be cloned to create new VMs, ensuring consistency and reducing the time needed to provision new instances. When a VM is created as a template, it is read-only and cannot be started, but can be cloned multiple times to create new VMs. -You can create a template directly in Proxmox by setting the `template` attribute to `true` when creating the VM resource: +You can create a template with Terraform by setting the `template` attribute to `true` when creating the VM resource: {{ codefile "terraform" "examples/guides/clone-vm/template.tf" }} -Once you have a template, you can clone it to create new VMs. The cloned VMs will inherit all the configuration from the template but can be customized further as needed. +Once you have a template, you can clone it to create new VMs. The cloned VMs will inherit all configuration from the template but can be customized further as needed. {{ codefile "terraform" "examples/guides/clone-vm/clone.tf" }} + +Full example is available in the [examples/guides/clone-vm](https://github.com/bpg/terraform-provider-proxmox/tree/main/examples/guides/clone-vm) directory. From 61619690cce176948e4795a98075c0854a42669b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 07:59:16 -0400 Subject: [PATCH 02/10] =?UTF-8?q?chore(ci):=20update=20actions/create-gith?= =?UTF-8?q?ub-app-token=20action=20(v2.0.6=20=E2=86=92=20v2.1.0)=20(#2095)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | datasource | package | from | to | | ----------- | ------------------------------- | ------ | ------ | | github-tags | actions/create-github-app-token | v2.0.6 | v2.1.0 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/link-check.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/release-please.yml | 2 +- .github/workflows/stale.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml index 56acf5f4..6c96b496 100644 --- a/.github/workflows/link-check.yml +++ b/.github/workflows/link-check.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate Short Lived OAuth App Token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: app-token with: app-id: "${{ secrets.BOT_APP_ID }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5e2d2aa3..36d6a391 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate Short Lived OAuth App Token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: app-token with: app-id: "${{ secrets.BOT_APP_ID }}" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 8ce0d0b1..5453123a 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: Generate Short Lived OAuth App Token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: app-token with: app-id: "${{ secrets.BOT_APP_ID }}" diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 01c23749..c54fda9f 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -12,7 +12,7 @@ jobs: pull-requests: write steps: - name: Generate Short Lived OAuth App Token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: app-token with: app-id: "${{ secrets.BOT_APP_ID }}" From 7c98464783a70e1ca801765eb5c16fed4ce0f329 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 07:59:36 -0400 Subject: [PATCH 03/10] =?UTF-8?q?chore(ci):=20update=20lycheeverse/lychee-?= =?UTF-8?q?action=20action=20(v2.4.1=20=E2=86=92=20v2.5.0)=20(#2096)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(ci): update actions/create-github-app-token action (v2.0.6 → v2.1.0) | datasource | package | from | to | | ----------- | ------------------------------- | ------ | ------ | | github-tags | actions/create-github-app-token | v2.0.6 | v2.1.0 | * chore(ci): update lycheeverse/lychee-action action (v2.4.1 → v2.5.0) | datasource | package | from | to | | ----------- | ------------------------- | ------ | ------ | | github-tags | lycheeverse/lychee-action | v2.4.1 | v2.5.0 | --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/link-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml index 6c96b496..8687bc8b 100644 --- a/.github/workflows/link-check.yml +++ b/.github/workflows/link-check.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Link Checker - uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1 + uses: lycheeverse/lychee-action@5c4ee84814c983aa7164eaee476f014e53ff3963 # v2.5.0 id: lychee env: GITHUB_TOKEN: "${{ steps.app-token.outputs.token }}" From 3855cb293fd32486eb39aee056c3c526d4f15ba8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 23:32:06 -0400 Subject: [PATCH 04/10] chore(ci): Update actions/checkout action (#2098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(ci): update actions/checkout digest (11bd719 → 08eba0b) * chore(ci): update actions/create-github-app-token action (v2.1.0 → v2.1.1) | datasource | package | from | to | | ----------- | ------------------------------- | ------ | ------ | | github-tags | actions/create-github-app-token | v2.1.0 | v2.1.1 | * chore(ci): update actions/checkout action (v4.2.2 → v4.3.0) | datasource | package | from | to | | ----------- | ---------------- | ------ | ------ | | github-tags | actions/checkout | v4.2.2 | v4.3.0 | * chore(ci): Update actions/checkout action | datasource | package | from | to | | ----------- | ---------------- | ------ | ------ | | github-tags | actions/checkout | v4 | v5 | | github-tags | actions/checkout | v4.2.2 | v5.0.0 | --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/code-quality.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/link-check.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 4 ++-- .github/workflows/testacc.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 9feb9eb5..55ca1d4a 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -16,7 +16,7 @@ jobs: pull-requests: write checks: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit fetch-depth: 0 # a full history is required for pull request analysis diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index ef8dfb98..679da4c4 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml index 8687bc8b..d48ec79a 100644 --- a/.github/workflows/link-check.yml +++ b/.github/workflows/link-check.yml @@ -19,7 +19,7 @@ jobs: repositories: "${{ github.event.repository.name }}" - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Link Checker uses: lycheeverse/lychee-action@5c4ee84814c983aa7164eaee476f014e53ff3963 # v2.5.0 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 36d6a391..bb469716 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,7 +32,7 @@ jobs: repositories: "${{ github.event.repository.name }}" - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95213691..027f99f1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: timeout-minutes: 5 steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 1 @@ -51,7 +51,7 @@ jobs: run: echo "$GITHUB_CONTEXT" - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 1 diff --git a/.github/workflows/testacc.yml b/.github/workflows/testacc.yml index e0852070..07017613 100644 --- a/.github/workflows/testacc.yml +++ b/.github/workflows/testacc.yml @@ -35,7 +35,7 @@ jobs: run: echo "$GITHUB_CONTEXT" - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 1 ref: ${{ github.event.inputs.ref || github.ref}} From 7f5d77143a2c3a607ee3f647ad2d9054b9e0d00d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 23:33:52 -0400 Subject: [PATCH 05/10] =?UTF-8?q?chore(ci):=20update=20actions/create-gith?= =?UTF-8?q?ub-app-token=20action=20(v2.1.0=20=E2=86=92=20v2.1.1)=20(#2099)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | datasource | package | from | to | | ----------- | ------------------------------- | ------ | ------ | | github-tags | actions/create-github-app-token | v2.1.0 | v2.1.1 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/link-check.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/release-please.yml | 2 +- .github/workflows/stale.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml index d48ec79a..a9858b66 100644 --- a/.github/workflows/link-check.yml +++ b/.github/workflows/link-check.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate Short Lived OAuth App Token - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: app-token with: app-id: "${{ secrets.BOT_APP_ID }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bb469716..40083aa0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate Short Lived OAuth App Token - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: app-token with: app-id: "${{ secrets.BOT_APP_ID }}" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 5453123a..f7982586 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: Generate Short Lived OAuth App Token - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: app-token with: app-id: "${{ secrets.BOT_APP_ID }}" diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index c54fda9f..5c5be86f 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -12,7 +12,7 @@ jobs: pull-requests: write steps: - name: Generate Short Lived OAuth App Token - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: app-token with: app-id: "${{ secrets.BOT_APP_ID }}" From b2c50120ea552d078e9634228f8b90b356a163b9 Mon Sep 17 00:00:00 2001 From: maidlover <117573165+maidl0ver@users.noreply.github.com> Date: Tue, 12 Aug 2025 22:46:34 +0000 Subject: [PATCH 06/10] feat(lxc): Add missing configuration options for container rootfs (#2067) * Add mount_options for container rootfs Signed-off-by: maidl0ver Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Rename mount key to mountoptions Signed-off-by: maidl0ver Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Add a line to the docs Signed-off-by: maidl0ver Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Add mount_options to a test Signed-off-by: maidl0ver Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * lint Signed-off-by: maidl0ver Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * feat(firewall): adds forward_policy to cluster firewall (#2064) Signed-off-by: Marshall Ford Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * fix: update container image URL in acc test Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Add validation and diff suppression Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Add a default value check for mount options in containerRead Signed-off-by: maidl0ver Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Check for changes to mount options Signed-off-by: maidl0ver Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Add disk change detection Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Update schema Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Add disk size to container update Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Remove redundant datastore ID Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Change type assertion Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Change volume name for containers Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Add fields to containerRead Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Change default disk mount options value to nil Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * Set volume format for container creation Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> * fix(lxc): root fs creation for storage-backed mp Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * fix rootfs unmarshalling from API response Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * fixes for edge cases Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * fix linter Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --------- Signed-off-by: maidl0ver Signed-off-by: maidlover <117573165+maidl0ver@users.noreply.github.com> Signed-off-by: Marshall Ford Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Marshall Ford Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- .../virtual_environment_container.md | 1 + fwprovider/test/resource_container_test.go | 60 ++++++- proxmox/nodes/containers/containers_types.go | 6 +- proxmoxtf/resource/container/container.go | 148 +++++++++++++++--- 4 files changed, 189 insertions(+), 26 deletions(-) diff --git a/docs/resources/virtual_environment_container.md b/docs/resources/virtual_environment_container.md index 70c7c879..4b1f4118 100644 --- a/docs/resources/virtual_environment_container.md +++ b/docs/resources/virtual_environment_container.md @@ -134,6 +134,7 @@ output "ubuntu_container_public_key" { - `size` - (Optional) The size of the root filesystem in gigabytes (defaults to `4`). When set to 0 a directory or zfs/btrfs subvolume will be created. Requires `datastore_id` to be set. + - `mount_options` (Optional) List of extra mount options. - `initialization` - (Optional) The initialization configuration. - `dns` - (Optional) The DNS configuration. - `domain` - (Optional) The DNS search domain. diff --git a/fwprovider/test/resource_container_test.go b/fwprovider/test/resource_container_test.go index 3b4dc02e..93510379 100644 --- a/fwprovider/test/resource_container_test.go +++ b/fwprovider/test/resource_container_test.go @@ -48,7 +48,7 @@ func TestAccResourceContainer(t *testing.T) { FileName: ptr.Ptr(imageFileName), Node: ptr.Ptr(te.NodeName), Storage: ptr.Ptr(te.DatastoreID), - URL: ptr.Ptr(fmt.Sprintf("%s/images/system/ubuntu-23.04-standard_23.04-1_amd64.tar.zst", te.ContainerImagesServer)), + URL: ptr.Ptr(fmt.Sprintf("%s/images/system/ubuntu-24.10-standard_24.10-1_amd64.tar.zst", te.ContainerImagesServer)), }) require.NoError(t, err) @@ -110,9 +110,10 @@ func TestAccResourceContainer(t *testing.T) { "device_passthrough.0.mode": "0660", "initialization.0.dns.#": "0", }), - ResourceAttributesSet(accTestContainerName, []string{ - "ipv4.vmbr0", - }), + // TODO: depends on DHCP, which may not work in some environments + // ResourceAttributesSet(accTestContainerName, []string{ + // "ipv4.vmbr0", + // }), func(*terraform.State) error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -142,6 +143,7 @@ func TestAccResourceContainer(t *testing.T) { disk { datastore_id = "local-lvm" size = 4 + mount_options = ["discard"] } mount_point { volume = "local-lvm" @@ -178,6 +180,56 @@ func TestAccResourceContainer(t *testing.T) { "description": "my\ndescription\nvalue\n", "device_passthrough.#": "1", "initialization.0.dns.#": "0", + "disk.0.mount_options.#": "1", + }), + ), + }, + { + // remove disk options + Config: te.RenderConfig(` + resource "proxmox_virtual_environment_container" "test_container" { + node_name = "{{.NodeName}}" + vm_id = {{.TestContainerID}} + timeout_delete = 10 + unprivileged = true + disk { + datastore_id = "local-lvm" + size = 4 + mount_options = [] + } + mount_point { + volume = "local-lvm" + size = "4G" + path = "mnt/local" + } + device_passthrough { + path = "/dev/zero" + } + description = <<-EOT + my + description + value + EOT + initialization { + hostname = "test" + ip_config { + ipv4 { + address = "172.16.10.10/15" + gateway = "172.16.0.1" + } + } + } + network_interface { + name = "vmbr0" + } + operating_system { + template_file_id = "local:vztmpl/{{.ImageFileName}}" + type = "ubuntu" + } + }`, WithRootUser()), + Check: resource.ComposeTestCheckFunc( + ResourceAttributes(accTestContainerName, map[string]string{ + "disk.0.mount_options.#": "0", }), ), }, diff --git a/proxmox/nodes/containers/containers_types.go b/proxmox/nodes/containers/containers_types.go index 1b1d8825..3663e2d1 100644 --- a/proxmox/nodes/containers/containers_types.go +++ b/proxmox/nodes/containers/containers_types.go @@ -546,7 +546,7 @@ func (r *CustomRootFS) EncodeValues(key string, v *url.Values) error { if r.MountOptions != nil { if len(*r.MountOptions) > 0 { - values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountOptions, ";"))) + values = append(values, fmt.Sprintf("mountoptions=%s", strings.Join(*r.MountOptions, ";"))) } } @@ -875,6 +875,8 @@ func (r *CustomRootFS) UnmarshalJSON(b []byte) error { r.Volume = v[0] } else if len(v) == 2 { switch v[0] { + case "volume": + r.Volume = v[1] case "acl": bv := types.CustomBool(v[1] == "1") r.ACL = &bv @@ -902,7 +904,7 @@ func (r *CustomRootFS) UnmarshalJSON(b []byte) error { case "size": r.Size = new(types.DiskSize) - err := r.Size.UnmarshalJSON([]byte(v[1])) + err = r.Size.UnmarshalJSON([]byte(v[1])) if err != nil { return fmt.Errorf("failed to unmarshal disk size: %w", err) } diff --git a/proxmoxtf/resource/container/container.go b/proxmoxtf/resource/container/container.go index 3b675d21..f64445d5 100644 --- a/proxmoxtf/resource/container/container.go +++ b/proxmoxtf/resource/container/container.go @@ -52,7 +52,10 @@ const ( dvCPUUnits = 1024 dvDescription = "" dvDevicePassthroughMode = "0660" + dvDiskACL = false dvDiskDatastoreID = "local" + dvDiskQuota = false + dvDiskReplicate = false dvDiskSize = 4 dvFeaturesNesting = false dvFeaturesKeyControl = false @@ -107,7 +110,11 @@ const ( mkCPUUnits = "units" mkDescription = "description" mkDisk = "disk" + mkDiskACL = "acl" mkDiskDatastoreID = "datastore_id" + mkDiskMountOptions = "mount_options" + mkDiskQuota = "quota" + mkDiskReplicate = "replicate" mkDiskSize = "size" mkFeatures = "features" mkFeaturesNesting = "nesting" @@ -329,13 +336,20 @@ func Container() *schema.Resource { DefaultFunc: func() (interface{}, error) { return []interface{}{ map[string]interface{}{ - mkDiskDatastoreID: dvDiskDatastoreID, - mkDiskSize: dvDiskSize, + mkDiskDatastoreID: dvDiskDatastoreID, + mkDiskSize: dvDiskSize, + mkDiskMountOptions: nil, }, }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + mkDiskACL: { + Type: schema.TypeBool, + Description: "Explicitly enable or disable ACL support", + Optional: true, + Default: dvDiskACL, + }, mkDiskDatastoreID: { Type: schema.TypeString, Description: "The datastore id", @@ -343,6 +357,18 @@ func Container() *schema.Resource { ForceNew: true, Default: dvDiskDatastoreID, }, + mkDiskQuota: { + Type: schema.TypeBool, + Description: "Enable user quotas for the container rootfs", + Optional: true, + Default: dvDiskQuota, + }, + mkDiskReplicate: { + Type: schema.TypeBool, + Description: "Will include this volume to a storage replica job", + Optional: true, + Default: dvDiskReplicate, + }, mkDiskSize: { Type: schema.TypeInt, Description: "The rootfs size in gigabytes", @@ -351,6 +377,17 @@ func Container() *schema.Resource { Default: dvDiskSize, ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)), }, + mkDiskMountOptions: { + Type: schema.TypeList, + Description: "Extra mount options", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + DiffSuppressFunc: structure.SuppressIfListsAreEqualIgnoringOrder, + DiffSuppressOnRefresh: true, + }, }, }, MaxItems: 1, @@ -1458,6 +1495,23 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf return diag.FromErr(err) } + vmIDUntyped, hasVMID := d.GetOk(mkVMID) + vmID := vmIDUntyped.(int) + + if !hasVMID { + vmIDNew, err := config.GetIDGenerator().NextID(ctx) + if err != nil { + return diag.FromErr(err) + } + + vmID = vmIDNew + + err = d.Set(mkVMID, vmID) + if err != nil { + return diag.FromErr(err) + } + } + nodeName := d.Get(mkNodeName).(string) container := Container() @@ -1709,12 +1763,21 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf var rootFS *containers.CustomRootFS + diskMountOptions := []string{} + + if diskBlock[mkDiskMountOptions] != nil { + for _, opt := range diskBlock[mkDiskMountOptions].([]any) { + diskMountOptions = append(diskMountOptions, opt.(string)) + } + } + diskSize := diskBlock[mkDiskSize].(int) if diskDatastoreID != "" && (diskSize != dvDiskSize || len(mountPoints) > 0) { // This is a special case where the rootfs size is set to a non-default value at creation time. // see https://pve.proxmox.com/pve-docs/chapter-pct.html#_storage_backed_mount_points rootFS = &containers.CustomRootFS{ - Volume: fmt.Sprintf("%s:%d", diskDatastoreID, diskSize), + Volume: fmt.Sprintf("%s:%d", diskDatastoreID, diskSize), + MountOptions: &diskMountOptions, } } @@ -1831,22 +1894,6 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf tags := d.Get(mkTags).([]interface{}) template := types.CustomBool(d.Get(mkTemplate).(bool)) unprivileged := types.CustomBool(d.Get(mkUnprivileged).(bool)) - vmIDUntyped, hasVMID := d.GetOk(mkVMID) - vmID := vmIDUntyped.(int) - - if !hasVMID { - vmIDNew, err := config.GetIDGenerator().NextID(ctx) - if err != nil { - return diag.FromErr(err) - } - - vmID = vmIDNew - - err = d.Set(mkVMID, vmID) - if err != nil { - return diag.FromErr(err) - } - } // Attempt to create the container using the retrieved values. createBody := containers.CreateRequestBody{ @@ -2253,12 +2300,22 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d if containerConfig.RootFS != nil { volumeParts := strings.Split(containerConfig.RootFS.Volume, ":") + disk[mkDiskACL] = containerConfig.RootFS.ACL + disk[mkDiskReplicate] = containerConfig.RootFS.Replicate + disk[mkDiskQuota] = containerConfig.RootFS.Quota disk[mkDiskDatastoreID] = volumeParts[0] + disk[mkDiskSize] = containerConfig.RootFS.Size.InGigabytes() + if containerConfig.RootFS.MountOptions != nil { + disk[mkDiskMountOptions] = *containerConfig.RootFS.MountOptions + } else { + disk[mkDiskMountOptions] = []string{} + } } else { // Default value of "storage" is "local" according to the API documentation. disk[mkDiskDatastoreID] = "local" disk[mkDiskSize] = dvDiskSize + disk[mkDiskMountOptions] = []string{} } currentDisk := d.Get(mkDisk).([]interface{}) @@ -2275,7 +2332,10 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d } } else if len(currentDisk) > 0 || disk[mkDiskDatastoreID] != dvDiskDatastoreID || - disk[mkDiskSize] != dvDiskSize { + disk[mkDiskACL] != dvDiskACL || + disk[mkDiskReplicate] != dvDiskReplicate || + disk[mkDiskQuota] != dvDiskQuota || + len(disk[mkDiskMountOptions].([]string)) > 0 { err := d.Set(mkDisk, []interface{}{disk}) diags = append(diags, diag.FromErr(err)...) } @@ -2917,6 +2977,50 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) updateBody.CPUUnits = &cpuUnits } + if d.HasChange(mkDisk) { + diskBlock, err := structure.GetSchemaBlock( + container, + d, + []string{mkDisk}, + 0, + true, + ) + if err != nil { + return diag.FromErr(err) + } + + rootFS := &containers.CustomRootFS{} + // Disk ID for the rootfs is always 0 + diskID := 0 + vmID := d.Get(mkVMID).(int) + rootFS.Volume = diskBlock[mkDiskDatastoreID].(string) + rootFS.Volume = getContainerDiskVolume(rootFS.Volume, vmID, diskID) + + acl := types.CustomBool(diskBlock[mkDiskACL].(bool)) + mountOptions := diskBlock[mkDiskMountOptions].([]interface{}) + quota := types.CustomBool(diskBlock[mkDiskQuota].(bool)) + replicate := types.CustomBool(diskBlock[mkDiskReplicate].(bool)) + size := types.DiskSizeFromGigabytes(int64(diskBlock[mkDiskSize].(int))) + + rootFS.ACL = &acl + rootFS.Quota = "a + rootFS.Replicate = &replicate + rootFS.Size = size + + mountOptionsStrings := make([]string, 0, len(mountOptions)) + + for _, option := range mountOptions { + mountOptionsStrings = append(mountOptionsStrings, option.(string)) + } + + // Always set, including empty, to allow clearing mount options + rootFS.MountOptions = &mountOptionsStrings + + updateBody.RootFS = rootFS + + rebootRequired = true + } + if d.HasChange(mkFeatures) { features, err := containerGetFeatures(container, d) if err != nil { @@ -3424,3 +3528,7 @@ func parseImportIDWithNodeName(id string) (string, string, error) { return nodeName, id, nil } + +func getContainerDiskVolume(rawVolume string, vmID int, diskID int) string { + return fmt.Sprintf("%s:vm-%d-disk-%d", rawVolume, vmID, diskID) +} From 0dec643b3dc670c48f3f79752cb93519e3515a36 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 22:00:50 -0400 Subject: [PATCH 07/10] =?UTF-8?q?chore(ci):=20update=20jetbrains/qodana-ac?= =?UTF-8?q?tion=20action=20(v2025.1.1=20=E2=86=92=20v2025.2.1)=20(#2106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | datasource | package | from | to | | ----------- | ----------------------- | --------- | --------- | | github-tags | JetBrains/qodana-action | v2025.1.1 | v2025.2.1 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/code-quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 55ca1d4a..cf7713b3 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -21,7 +21,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit fetch-depth: 0 # a full history is required for pull request analysis - name: 'Qodana Scan' - uses: JetBrains/qodana-action@e14351bdf4707c4cecc25a86a9190745b7b40de8 # v2025.1.1 + uses: JetBrains/qodana-action@27de2a744479d1d731934eeaf79287575ebc5dd3 # v2025.2.1 with: post-pr-comment: false env: From 9e10206e19b9f3cdf5989bef6956a4a1242a7235 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 02:02:49 +0000 Subject: [PATCH 08/10] =?UTF-8?q?chore(deps):=20update=20image=20golang=20?= =?UTF-8?q?(1.24.6=20=E2=86=92=201.25.0)=20(#2107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | datasource | package | from | to | | ---------- | ------- | ------ | ------ | | docker | golang | 1.24.6 | 1.25.0 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index feec47f6..174d90ff 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24.6@sha256:2c89c41fb9efc3807029b59af69645867cfe978d2b877d475be0d72f6c6ce6f6 +FROM golang:1.25.0@sha256:10a15b9d650c559eff6cb070f3177f1e2fc067cd7412e5ca97c9cb8167a924b7 ARG GOLANGCI_LINT_VERSION=2.3.1 # renovate: depName=golangci/golangci-lint datasource=github-releases From 21bed824e4cc4fd53cc927273798e5c6b757f89d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 17:51:51 -0400 Subject: [PATCH 09/10] =?UTF-8?q?chore(deps):=20update=20golangci/golangci?= =?UTF-8?q?-lint=20(v2.3.1=20=E2=86=92=20v2.4.0)=20(#2110)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(ci): update goreleaser/goreleaser-action action (v6.3.0 → v6.4.0) | datasource | package | from | to | | ----------- | ---------------------------- | ------ | ------ | | github-tags | goreleaser/goreleaser-action | v6.3.0 | v6.4.0 | * chore(deps): update golangci/golangci-lint (v2.3.1 → v2.4.0) | datasource | package | from | to | | --------------- | ---------------------- | ------ | ------ | | github-releases | golangci/golangci-lint | v2.3.1 | v2.4.0 | --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- .github/workflows/golangci-lint.yml | 2 +- Makefile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 174d90ff..fa42ff51 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ FROM golang:1.25.0@sha256:10a15b9d650c559eff6cb070f3177f1e2fc067cd7412e5ca97c9cb8167a924b7 -ARG GOLANGCI_LINT_VERSION=2.3.1 # renovate: depName=golangci/golangci-lint datasource=github-releases +ARG GOLANGCI_LINT_VERSION=2.4.0 # renovate: depName=golangci/golangci-lint datasource=github-releases RUN apt update && apt upgrade -y && \ apt-get install --no-install-recommends -y ca-certificates curl gnupg lsb-release jq zsh neovim gh && \ diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 679da4c4..fcfba8a7 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -42,6 +42,6 @@ jobs: if: ${{ steps.filter.outputs.go == 'true' || steps.filter.outputs.linter == 'true'}} uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8 with: - version: v2.3.1 # renovate: depName=golangci/golangci-lint datasource=github-releases + version: v2.4.0 # renovate: depName=golangci/golangci-lint datasource=github-releases skip-cache: true args: -v --timeout=10m diff --git a/Makefile b/Makefile index bb73b0dd..adab5364 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TARGETS=darwin linux windows TERRAFORM_PLUGIN_EXTENSION= VERSION=0.81.0# x-release-please-version -GOLANGCI_LINT_VERSION=2.3.1# renovate: depName=golangci/golangci-lint datasource=github-releases +GOLANGCI_LINT_VERSION=2.4.0# renovate: depName=golangci/golangci-lint datasource=github-releases # check if opentofu is installed and use it if it is, # otherwise use terraform From 420add86698425afba60ac06b3481b1057da5aee Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 17:52:06 -0400 Subject: [PATCH 10/10] =?UTF-8?q?chore(deps):=20update=20image=20golang=20?= =?UTF-8?q?(10a15b9=20=E2=86=92=209e56f0d)=20(#2109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index fa42ff51..625bcd04 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25.0@sha256:10a15b9d650c559eff6cb070f3177f1e2fc067cd7412e5ca97c9cb8167a924b7 +FROM golang:1.25.0@sha256:9e56f0d0f043a68bb8c47c819e47dc29f6e8f5129b8885bed9d43f058f7f3ed6 ARG GOLANGCI_LINT_VERSION=2.4.0 # renovate: depName=golangci/golangci-lint datasource=github-releases