From 486e650eddf05a2c6520040b5815544b6c369674 Mon Sep 17 00:00:00 2001 From: Syerikjan Khusayan Date: Wed, 20 Aug 2025 13:54:01 -0400 Subject: [PATCH 1/4] feat: make plugin version optional - installs latest version --- docs/resources/cloud_plugin_installation.md | 5 +++- .../resource_cloud_plugin_installation.go | 14 +++++++--- .../cloud/resource_cloud_plugin_test.go | 26 +++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/docs/resources/cloud_plugin_installation.md b/docs/resources/cloud_plugin_installation.md index 3c8c25d9e..7977ea148 100644 --- a/docs/resources/cloud_plugin_installation.md +++ b/docs/resources/cloud_plugin_installation.md @@ -38,7 +38,10 @@ resource "grafana_cloud_plugin_installation" "test" { - `slug` (String) Slug of the plugin to be installed. - `stack_slug` (String) The stack id to which the plugin should be installed. -- `version` (String) Version of the plugin to be installed. + +### Optional + +- `version` (String) Version of the plugin to be installed, latest version is installed when omitted. ### Read-Only diff --git a/internal/resources/cloud/resource_cloud_plugin_installation.go b/internal/resources/cloud/resource_cloud_plugin_installation.go index 14ecf4e08..a84f27d68 100644 --- a/internal/resources/cloud/resource_cloud_plugin_installation.go +++ b/internal/resources/cloud/resource_cloud_plugin_installation.go @@ -46,9 +46,9 @@ Required access policy scopes: ForceNew: true, }, "version": { - Description: "Version of the plugin to be installed.", + Description: "Version of the plugin to be installed, latest version is installed when omitted.", Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, }, @@ -92,10 +92,14 @@ func listStackPlugins(ctx context.Context, client *gcom.APIClient, data *ListerD func resourcePluginInstallationCreate(ctx context.Context, d *schema.ResourceData, client *gcom.APIClient) diag.Diagnostics { stackSlug := d.Get("stack_slug").(string) pluginSlug := d.Get("slug").(string) + version := "latest" + if v, ok := d.GetOk("version"); ok { + version = v.(string) + } req := gcom.PostInstancePluginsRequest{ Plugin: pluginSlug, - Version: common.Ref(d.Get("version").(string)), + Version: common.Ref(version), } err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { @@ -136,7 +140,9 @@ func resourcePluginInstallationRead(ctx context.Context, d *schema.ResourceData, d.Set("stack_slug", installation.InstanceSlug) d.Set("slug", installation.PluginSlug) - d.Set("version", installation.Version) + if _, ok := d.GetOk("version"); ok { + d.Set("version", installation.Version) + } d.SetId(resourcePluginInstallationID.Make(stackSlug, pluginSlug)) return nil diff --git a/internal/resources/cloud/resource_cloud_plugin_test.go b/internal/resources/cloud/resource_cloud_plugin_test.go index 08b7aa9a8..10e43803b 100644 --- a/internal/resources/cloud/resource_cloud_plugin_test.go +++ b/internal/resources/cloud/resource_cloud_plugin_test.go @@ -36,6 +36,17 @@ func TestAccResourcePluginInstallation(t *testing.T) { resource.TestCheckResourceAttr("grafana_cloud_plugin_installation.test-installation", "slug", "grafana-googlesheets-datasource"), resource.TestCheckResourceAttr("grafana_cloud_plugin_installation.test-installation", "version", "1.2.5")), }, + { + Config: testAccGrafanaCloudPluginInstallationNoVersion(stackSlug, pluginSlug), + Check: resource.ComposeTestCheckFunc( + testAccStackCheckExists("grafana_cloud_stack.test", &stack), + testAccCloudPluginInstallationCheckExists(stackSlug, pluginSlug), + resource.TestCheckResourceAttrSet("grafana_cloud_plugin_installation.test-installation-no-version", "id"), + resource.TestCheckResourceAttr("grafana_cloud_plugin_installation.test-installation-no-version", "stack_slug", stackSlug), + resource.TestCheckResourceAttr("grafana_cloud_plugin_installation.test-installation-no-version", "slug", pluginSlug), + // Don't check version attribute since it's not specified in config + ), + }, { ResourceName: "grafana_cloud_plugin_installation.test-installation", ImportState: true, @@ -105,3 +116,18 @@ func testAccGrafanaCloudPluginInstallation(stackSlug, name, version string) stri } `, stackSlug, name, version) } + +func testAccGrafanaCloudPluginInstallationNoVersion(stackSlug, name string) string { + return fmt.Sprintf(` + resource "grafana_cloud_stack" "test" { + name = "%[1]s" + slug = "%[1]s" + wait_for_readiness = false + } + resource "grafana_cloud_plugin_installation" "test-installation-no-version" { + stack_slug = grafana_cloud_stack.test.slug + slug = "%[2]s" + # version omitted - should install latest + } + `, stackSlug, name) +} From 813556fd1c3e48a4b84b0bf573eda90b4a9b8f63 Mon Sep 17 00:00:00 2001 From: Syerikjan Khusayan Date: Thu, 21 Aug 2025 12:28:53 -0400 Subject: [PATCH 2/4] docs: version won't auto-update --- docs/resources/cloud_plugin_installation.md | 2 +- internal/resources/cloud/resource_cloud_plugin_installation.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/cloud_plugin_installation.md b/docs/resources/cloud_plugin_installation.md index 7977ea148..a64b22ae4 100644 --- a/docs/resources/cloud_plugin_installation.md +++ b/docs/resources/cloud_plugin_installation.md @@ -41,7 +41,7 @@ resource "grafana_cloud_plugin_installation" "test" { ### Optional -- `version` (String) Version of the plugin to be installed, latest version is installed when omitted. +- `version` (String) Version of the plugin to be installed. When omitted, installs the latest available version at the time of creation. Will not auto-update to newer versions. If you already have a plugin installed and want to upgrade, specify the target version explicitly. ### Read-Only diff --git a/internal/resources/cloud/resource_cloud_plugin_installation.go b/internal/resources/cloud/resource_cloud_plugin_installation.go index a84f27d68..194863bb8 100644 --- a/internal/resources/cloud/resource_cloud_plugin_installation.go +++ b/internal/resources/cloud/resource_cloud_plugin_installation.go @@ -46,7 +46,7 @@ Required access policy scopes: ForceNew: true, }, "version": { - Description: "Version of the plugin to be installed, latest version is installed when omitted.", + Description: "Version of the plugin to be installed. When omitted, installs the latest available version at the time of creation. Will not auto-update to newer versions. If you already have a plugin installed and want to upgrade, specify the target version explicitly.", Type: schema.TypeString, Optional: true, ForceNew: true, From c64894e697412be5133c5e99db1486313b7f7ff4 Mon Sep 17 00:00:00 2001 From: Syerikjan Khusayan Date: Thu, 21 Aug 2025 18:45:30 -0400 Subject: [PATCH 3/4] ref: check plugin catalog latest version in read --- docs/resources/cloud_plugin_installation.md | 2 +- .../resource_cloud_plugin_installation.go | 14 +++++++++++-- .../cloud/resource_cloud_plugin_test.go | 21 ++++++++++--------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/docs/resources/cloud_plugin_installation.md b/docs/resources/cloud_plugin_installation.md index a64b22ae4..0432a4f21 100644 --- a/docs/resources/cloud_plugin_installation.md +++ b/docs/resources/cloud_plugin_installation.md @@ -41,7 +41,7 @@ resource "grafana_cloud_plugin_installation" "test" { ### Optional -- `version` (String) Version of the plugin to be installed. When omitted, installs the latest available version at the time of creation. Will not auto-update to newer versions. If you already have a plugin installed and want to upgrade, specify the target version explicitly. +- `version` (String) Version of the plugin to be installed. Defaults to 'latest' and installs the most recent version. Terraform will detect new version as drift for plan/apply. Defaults to `latest`. ### Read-Only diff --git a/internal/resources/cloud/resource_cloud_plugin_installation.go b/internal/resources/cloud/resource_cloud_plugin_installation.go index 194863bb8..6c4c7aed6 100644 --- a/internal/resources/cloud/resource_cloud_plugin_installation.go +++ b/internal/resources/cloud/resource_cloud_plugin_installation.go @@ -19,6 +19,8 @@ var ( ) ) +const LatestVersion = "latest" + func resourcePluginInstallation() *common.Resource { schema := &schema.Resource{ Description: ` @@ -46,9 +48,10 @@ Required access policy scopes: ForceNew: true, }, "version": { - Description: "Version of the plugin to be installed. When omitted, installs the latest available version at the time of creation. Will not auto-update to newer versions. If you already have a plugin installed and want to upgrade, specify the target version explicitly.", + Description: "Version of the plugin to be installed. Defaults to 'latest' and installs the most recent version. Terraform will detect new version as drift for plan/apply.", Type: schema.TypeString, Optional: true, + Default: LatestVersion, ForceNew: true, }, }, @@ -137,10 +140,17 @@ func resourcePluginInstallationRead(ctx context.Context, d *schema.ResourceData, if err, shouldReturn := common.CheckReadError("plugin", d, err); shouldReturn { return err } + catalogPlugin, _, err := client.PluginsAPI.GetPlugin(ctx, pluginSlug.(string)).Execute() + if err, shouldReturn := common.CheckReadError("plugin", d, err); shouldReturn { + return err + } d.Set("stack_slug", installation.InstanceSlug) d.Set("slug", installation.PluginSlug) - if _, ok := d.GetOk("version"); ok { + desiredVersion := d.Get("version").(string) + if desiredVersion == LatestVersion && installation.Version == catalogPlugin.Version { + d.Set("version", LatestVersion) + } else { d.Set("version", installation.Version) } d.SetId(resourcePluginInstallationID.Make(stackSlug, pluginSlug)) diff --git a/internal/resources/cloud/resource_cloud_plugin_test.go b/internal/resources/cloud/resource_cloud_plugin_test.go index 10e43803b..6ace326fc 100644 --- a/internal/resources/cloud/resource_cloud_plugin_test.go +++ b/internal/resources/cloud/resource_cloud_plugin_test.go @@ -37,14 +37,14 @@ func TestAccResourcePluginInstallation(t *testing.T) { resource.TestCheckResourceAttr("grafana_cloud_plugin_installation.test-installation", "version", "1.2.5")), }, { - Config: testAccGrafanaCloudPluginInstallationNoVersion(stackSlug, pluginSlug), + Config: testAccGrafanaCloudPluginInstallationLatest(stackSlug, "grafana-clock-panel"), Check: resource.ComposeTestCheckFunc( testAccStackCheckExists("grafana_cloud_stack.test", &stack), - testAccCloudPluginInstallationCheckExists(stackSlug, pluginSlug), + testAccCloudPluginInstallationCheckExists(stackSlug, "grafana-clock-panel"), resource.TestCheckResourceAttrSet("grafana_cloud_plugin_installation.test-installation-no-version", "id"), resource.TestCheckResourceAttr("grafana_cloud_plugin_installation.test-installation-no-version", "stack_slug", stackSlug), resource.TestCheckResourceAttr("grafana_cloud_plugin_installation.test-installation-no-version", "slug", pluginSlug), - // Don't check version attribute since it's not specified in config + resource.TestCheckResourceAttr("grafana_cloud_plugin_installation.test-installation-no-version", "version", "latest"), ), }, { @@ -106,6 +106,7 @@ func testAccGrafanaCloudPluginInstallation(stackSlug, name, version string) stri resource "grafana_cloud_stack" "test" { name = "%[1]s" slug = "%[1]s" + delete_protection = false wait_for_readiness = false } @@ -117,17 +118,17 @@ func testAccGrafanaCloudPluginInstallation(stackSlug, name, version string) stri `, stackSlug, name, version) } -func testAccGrafanaCloudPluginInstallationNoVersion(stackSlug, name string) string { +func testAccGrafanaCloudPluginInstallationLatest(stackSlug, name string) string { return fmt.Sprintf(` - resource "grafana_cloud_stack" "test" { - name = "%[1]s" - slug = "%[1]s" - wait_for_readiness = false - } + resource "grafana_cloud_stack" "test" { + name = "%[1]s" + slug = "%[1]s" + delete_protection = false + wait_for_readiness = false + } resource "grafana_cloud_plugin_installation" "test-installation-no-version" { stack_slug = grafana_cloud_stack.test.slug slug = "%[2]s" - # version omitted - should install latest } `, stackSlug, name) } From 20af507807ce73c0b44c789ec52ea5857de52b8f Mon Sep 17 00:00:00 2001 From: Syerikjan Khusayan Date: Fri, 22 Aug 2025 09:26:21 -0400 Subject: [PATCH 4/4] ref: only fetch catalog plugin version when desired version is the latest --- .../resource_cloud_plugin_installation.go | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/resources/cloud/resource_cloud_plugin_installation.go b/internal/resources/cloud/resource_cloud_plugin_installation.go index 6c4c7aed6..9278e3563 100644 --- a/internal/resources/cloud/resource_cloud_plugin_installation.go +++ b/internal/resources/cloud/resource_cloud_plugin_installation.go @@ -95,10 +95,7 @@ func listStackPlugins(ctx context.Context, client *gcom.APIClient, data *ListerD func resourcePluginInstallationCreate(ctx context.Context, d *schema.ResourceData, client *gcom.APIClient) diag.Diagnostics { stackSlug := d.Get("stack_slug").(string) pluginSlug := d.Get("slug").(string) - version := "latest" - if v, ok := d.GetOk("version"); ok { - version = v.(string) - } + version := d.Get("version").(string) req := gcom.PostInstancePluginsRequest{ Plugin: pluginSlug, @@ -140,15 +137,20 @@ func resourcePluginInstallationRead(ctx context.Context, d *schema.ResourceData, if err, shouldReturn := common.CheckReadError("plugin", d, err); shouldReturn { return err } - catalogPlugin, _, err := client.PluginsAPI.GetPlugin(ctx, pluginSlug.(string)).Execute() - if err, shouldReturn := common.CheckReadError("plugin", d, err); shouldReturn { - return err + desiredVersion := d.Get("version").(string) + catalogVersion := "" + if desiredVersion == LatestVersion { + catalogPlugin, _, err := client.PluginsAPI.GetPlugin(ctx, pluginSlug.(string)).Execute() + if err, shouldReturn := common.CheckReadError("plugin", d, err); shouldReturn { + return err + } + catalogVersion = catalogPlugin.Version } d.Set("stack_slug", installation.InstanceSlug) d.Set("slug", installation.PluginSlug) - desiredVersion := d.Get("version").(string) - if desiredVersion == LatestVersion && installation.Version == catalogPlugin.Version { + + if desiredVersion == LatestVersion && installation.Version == catalogVersion { d.Set("version", LatestVersion) } else { d.Set("version", installation.Version)