diff --git a/vault/resource_plugin.go b/vault/resource_plugin.go index 555ff3566..7fbc78ff3 100644 --- a/vault/resource_plugin.go +++ b/vault/resource_plugin.go @@ -7,7 +7,7 @@ import ( "context" "fmt" "log" - "strings" + "regexp" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -27,6 +27,34 @@ const ( fieldRuntime = "runtime" ) +var ( + // Version regex is intentionally loose, its main purpose is to disallow + // slashes so they can be used to delineate from the name. Version segment + // is optional. + pluginIDRegex = regexp.MustCompile(`^(auth|secret|database)(?:/version/([0-9a-zA-Z.-]+?))?/name/(.+)$`) +) + +func pluginFromID(id string) (typ string, name string, version string) { + matches := pluginIDRegex.FindStringSubmatch(id) + switch { + case matches == nil || len(matches) < 3: + return "", "", "" + case len(matches) == 3: + return matches[1], matches[2], "" + default: + return matches[1], matches[3], matches[2] + } +} + +func idFromPlugin(typ, name, version string) string { + if version == "" { + return fmt.Sprintf("%s/name/%s", typ, name) + + } + + return fmt.Sprintf("%s/version/%s/name/%s", typ, version, name) +} + func pluginResource() *schema.Resource { return &schema.Resource{ CreateContext: pluginWrite, @@ -109,10 +137,7 @@ func pluginWrite(ctx context.Context, d *schema.ResourceData, meta interface{}) } name := d.Get(consts.FieldName).(string) version := d.Get(consts.FieldVersion).(string) - id := fmt.Sprintf("%s/%s", pluginType, name) - if version != "" { - id = fmt.Sprintf("%s/%s", id, version) - } + id := idFromPlugin(pluginType.String(), name, version) if diagErr := versionedPluginsSupported(meta, version); diagErr != nil { return diagErr @@ -152,16 +177,9 @@ func pluginRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d return diag.FromErr(e) } - var typ, name, version string - parts := strings.Split(d.Id(), "/") - lenParts := len(parts) - switch lenParts { - case 0, 1: - return diag.Errorf("invalid ID %q, must be of form / or //", d.Id()) - case 2: - typ, name = parts[0], parts[1] - default: - typ, name, version = parts[0], strings.Join(parts[1:lenParts-1], "/"), parts[lenParts-1] + typ, name, version := pluginFromID(d.Id()) + if typ == "" || name == "" { + diag.Errorf("invalid ID %q, must be of form :type/name/:name or :type/version/:version/name/:name", d.Id()) } if diagErr := versionedPluginsSupported(meta, version); diagErr != nil { diff --git a/vault/resource_plugin_pinned_version_test.go b/vault/resource_plugin_pinned_version_test.go index 9da7fdfd8..5f95ca0c0 100644 --- a/vault/resource_plugin_pinned_version_test.go +++ b/vault/resource_plugin_pinned_version_test.go @@ -60,7 +60,7 @@ resource "vault_plugin_pinned_version" "test" { name = vault_plugin.test.name version = vault_plugin.test.version } -`, testPluginConfig(pluginType, name, version, sha256, os.Getenv(envPluginCommand), `["--arg"]`, `["foo=bar"]`, true)) +`, testPluginConfig(pluginType, name, version, sha256, os.Getenv(envPluginCommand), `["--arg"]`, `["foo=bar"]`)) return ret } diff --git a/vault/resource_plugin_test.go b/vault/resource_plugin_test.go index 7cb161711..00ebc7c3a 100644 --- a/vault/resource_plugin_test.go +++ b/vault/resource_plugin_test.go @@ -39,26 +39,21 @@ func TestPlugin(t *testing.T) { sha256Updated := strings.Repeat("12345678", 8) cmd := os.Getenv(envPluginCommand) - versionsSupported := true - if testProvider != nil { - m := testProvider.Meta().(*provider.ProviderMeta) - versionsSupported = m.GetVaultVersion().GreaterThanOrEqual(provider.VaultVersion112) - } - resource.Test(t, resource.TestCase{ ProviderFactories: providerFactories, PreCheck: func() { testutil.TestAccPreCheck(t) testutil.SkipTestEnvUnset(t, envPluginCommand) + SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion112) }, Steps: []resource.TestStep{ { - Config: testPluginConfig(typ, destName, version, sha256, cmd, args, env, versionsSupported), + Config: testPluginConfig(typ, destName, version, sha256, cmd, args, env), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, consts.FieldType, typ), resource.TestCheckResourceAttr(resourceName, consts.FieldName, destName), - testCheckVersionAttr(resourceName, version, versionsSupported), + resource.TestCheckResourceAttr(resourceName, consts.FieldVersion, destName), resource.TestCheckResourceAttr(resourceName, fieldSHA256, sha256), resource.TestCheckResourceAttr(resourceName, fieldCommand, cmd), testValidateList(resourceName, fieldArgs, []string{"--foo"}), @@ -66,12 +61,12 @@ func TestPlugin(t *testing.T) { ), }, { - Config: testPluginConfig(typ, destName, version, sha256Updated, cmd, argsUpdated, envUpdated, versionsSupported), + Config: testPluginConfig(typ, destName, version, sha256Updated, cmd, argsUpdated, envUpdated), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, consts.FieldType, typ), resource.TestCheckResourceAttr(resourceName, consts.FieldName, destName), - testCheckVersionAttr(resourceName, version, versionsSupported), + resource.TestCheckResourceAttr(resourceName, consts.FieldVersion, destName), resource.TestCheckResourceAttr(resourceName, fieldSHA256, sha256Updated), resource.TestCheckResourceAttr(resourceName, fieldCommand, cmd), testValidateList(resourceName, fieldArgs, []string{"--bar"}), @@ -83,20 +78,7 @@ func TestPlugin(t *testing.T) { }) } -func testPluginConfig(pluginType, name, version, sha256, command, args, env string, versionsSupported bool) string { - if !versionsSupported { - return fmt.Sprintf(` -resource "vault_plugin" "test" { - type = "%s" - name = "%s" - sha256 = "%s" - command = "%s" - args = %s - env = %s -} -`, pluginType, name, sha256, command, args, env) - } - +func testPluginConfig(pluginType, name, version, sha256, command, args, env string) string { return fmt.Sprintf(` resource "vault_plugin" "test" { type = "%s" @@ -133,12 +115,35 @@ func testValidateList(resourceName, attr string, expected []string) resource.Tes } } -func testCheckVersionAttr(resourceName, expected string, versionsSupported bool) resource.TestCheckFunc { - if versionsSupported { - return resource.TestCheckResourceAttr(resourceName, consts.FieldVersion, expected) - } - - return func(*terraform.State) error { - return nil +func TestPluginFromID(t *testing.T) { + for name, tc := range map[string]struct { + id string + typ string + name string + version string + }{ + "auth": {"auth/version/v1.0.0/name/foo", "auth", "foo", "v1.0.0"}, + "secret": {"secret/version/v1.0.0/name/foo", "secret", "foo", "v1.0.0"}, + "database": {"database/version/v1.0.0/name/foo", "database", "foo", "v1.0.0"}, + "no version": {"auth/name/foo", "auth", "foo", ""}, + "weird version": {"auth/version/bad-semver/name/foo", "auth", "foo", "bad-semver"}, + "name with slashes": {"auth/version/v1.0.0/name/foo/bar/baz", "auth", "foo/bar/baz", "v1.0.0"}, + "no version and name with slashes": {"auth/name/foo/bar/baz", "auth", "foo/bar/baz", ""}, + "missing type": {"version/v1.0.0/name/foo", "", "", ""}, + "invalid type": {"new-type/version/v1.0.0/name/foo", "", "", ""}, + "missing name": {"auth/version/v1.0.0", "", "", ""}, + } { + t.Run(name, func(t *testing.T) { + typ, name, version := pluginFromID(tc.id) + if typ != tc.typ { + t.Errorf("expected type %q, got %q", tc.typ, typ) + } + if name != tc.name { + t.Errorf("expected name %q, got %q", tc.name, name) + } + if version != tc.version { + t.Errorf("expected version %q, got %q", tc.version, version) + } + }) } }