diff --git a/docs/docs/03-tools/04-credential-tools.md b/docs/docs/03-tools/04-credential-tools.md index 7178542d..981a708c 100644 --- a/docs/docs/03-tools/04-credential-tools.md +++ b/docs/docs/03-tools/04-credential-tools.md @@ -148,10 +148,6 @@ This is useful when working with credential overrides. ## Credential Overrides (Advanced) -:::note -The syntax for this will change at some point in the future. -::: - You can bypass credential tools and stored credentials by setting the `--credential-override` argument (or the `GPTSCRIPT_CREDENTIAL_OVERRIDE` environment variable) when running GPTScript. To set up a credential override, you need to be aware of which environment variables the credential tool sets. You can find this out by running the @@ -166,41 +162,24 @@ This can be overridden with a credential alias, i.e. `credential: my-cred-tool.g If the credential has an alias, use it instead of the tool name when you specify an override. ::: -The `--credential-override` argument must be formatted in one of the following three ways: +The `--credential-override` argument must be formatted in one of the following two ways: #### 1. Key-Value Pairs -`toolA:ENV_VAR_1=value1,ENV_VAR_2=value2;toolB:ENV_VAR_1=value3,ENV_VAR_2=value4` +`toolA:ENV_VAR_1=value1,ENV_VAR_2=value2` + +In this example, both `toolA` provides the variables `ENV_VAR_1` and `ENV_VAR_2`. +This will set the environment variables `ENV_VAR_1` and `ENV_VAR_2` to the specific values `value1` and `value2`. -In this example, both `toolA` and `toolB` provide the variables `ENV_VAR_1` and `ENV_VAR_2`. -This will set the environment variables `ENV_VAR_1` and `ENV_VAR_2` to the specific values provided for each tool. +:::info +To override more than one credential, use `;` as a separator. For example, `toolA:ENV_VAR_1=value1;toolB:ENV_VAR_2=value2`. +::: #### 2. Environment Variables -`toolA:ENV_VAR_1,ENV_VAR_2;toolB:ENV_VAR_3,ENV_VAR_4` +`toolA:ENV_VAR_1,ENV_VAR_2` -In this example, `toolA` provides the variables `ENV_VAR_1` and `ENV_VAR_2`, and `toolB` provides the variables `ENV_VAR_3` and `ENV_VAR_4`. -This will read the values of `ENV_VAR_1` through `ENV_VAR_4` from the current environment and set them for each tool. +In this example, `toolA` provides the variables `ENV_VAR_1` and `ENV_VAR_2`, +This will read the values of `ENV_VAR_1` through `ENV_VAR_4` from the current environment and set them for the credential. This is a direct mapping of environment variable names. **This is not recommended when overriding credentials for multiple tools that use the same environment variable names.** - -#### 3. Environment Variable Mapping - -`toolA:ENV_VAR_1->TOOL_A_ENV_VAR_1,ENV_VAR_2->TOOL_A_ENV_VAR_2;toolB:ENV_VAR_1->TOOL_B_ENV_VAR_1,ENV_VAR_2->TOOL_B_ENV_VAR_2` - -In this example, `toolA` and `toolB` both provide the variables `ENV_VAR_1` and `ENV_VAR_2`. -This will set the environment variables `ENV_VAR_1` and `ENV_VAR_2` to the values of `TOOL_A_ENV_VAR_1` and -`TOOL_A_ENV_VAR_2` from the current environment for `toolA`. The same applies for `toolB`, but with the values of -`TOOL_B_ENV_VAR_1` and `TOOL_B_ENV_VAR_2`. This is a mapping of one environment variable name to another. - -### Real-World Example - -Here is an example of how you can use a credential override to skip running the credential tool for the Brave Search tool: - -```bash -gptscript --credential-override "github.com/gptscript-ai/search/brave-credential:GPTSCRIPT_BRAVE_SEARCH_TOKEN->MY_BRAVE_SEARCH_TOKEN" github.com/gptscript-ai/search/brave '{"q": "cute cats"}' -``` - -If you run this command, rather than being prompted by the credential tool for your token, GPTScript will read the contents -of the environment variable `MY_BRAVE_SEARCH_TOKEN` and set that as the variable `GPTSCRIPT_BRAVE_SEARCH_TOKEN` when it runs -the script. diff --git a/pkg/runner/credentials.go b/pkg/runner/credentials.go index c547e832..f1c5d0dd 100644 --- a/pkg/runner/credentials.go +++ b/pkg/runner/credentials.go @@ -7,10 +7,9 @@ import ( ) // parseCredentialOverrides parses a string of credential overrides that the user provided as a command line arg. -// The format of credential overrides can be one of three things: +// The format of credential overrides can be one of two things: // cred1:ENV1,ENV2;cred2:ENV1,ENV2 (direct mapping of environment variables) // cred1:ENV1=VALUE1,ENV2=VALUE2;cred2:ENV1=VALUE1,ENV2=VALUE2 (key-value pairs) -// cred1:ENV1->OTHER_ENV1,ENV2->OTHER_ENV2;cred2:ENV1->OTHER_ENV1,ENV2->OTHER_ENV2 (mapping to other environment variables) // // This function turns it into a map[string]map[string]string like this: // @@ -36,15 +35,8 @@ func parseCredentialOverrides(override string) (map[string]map[string]string, er for _, env := range strings.Split(envs, ",") { key, value, found := strings.Cut(env, "=") if !found { - var envVar string - key, envVar, found = strings.Cut(env, "->") - if found { - // User did a mapping of key -> other env var, so look up the value. - value = os.Getenv(envVar) - } else { - // User just passed an env var name as the key, so look up the value. - value = os.Getenv(key) - } + // User just passed an env var name as the key, so look up the value. + value = os.Getenv(key) } envMap[key] = value } diff --git a/pkg/runner/credentials_test.go b/pkg/runner/credentials_test.go new file mode 100644 index 00000000..548bc3b5 --- /dev/null +++ b/pkg/runner/credentials_test.go @@ -0,0 +1,128 @@ +package runner + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseCredentialOverrides(t *testing.T) { + cases := []struct { + name string + envs map[string]string + in string + out map[string]map[string]string + expectErr bool + }{ + { + name: "empty", + in: "", + expectErr: true, + }, + { + name: "single cred, single env", + envs: map[string]string{ + "ENV1": "VALUE1", + }, + in: "cred1:ENV1", + out: map[string]map[string]string{ + "cred1": { + "ENV1": "VALUE1", + }, + }, + }, + { + name: "single cred, multiple envs", + envs: map[string]string{ + "ENV1": "VALUE1", + "ENV2": "VALUE2", + }, + in: "cred1:ENV1,ENV2", + out: map[string]map[string]string{ + "cred1": { + "ENV1": "VALUE1", + "ENV2": "VALUE2", + }, + }, + }, + { + name: "single cred, key value pairs", + envs: map[string]string{ + "ENV1": "VALUE1", + "ENV2": "VALUE2", + }, + in: "cred1:ENV1=OTHERVALUE1,ENV2=OTHERVALUE2", + out: map[string]map[string]string{ + "cred1": { + "ENV1": "OTHERVALUE1", + "ENV2": "OTHERVALUE2", + }, + }, + }, + { + name: "multiple creds, multiple envs", + envs: map[string]string{ + "ENV1": "VALUE1", + "ENV2": "VALUE2", + }, + in: "cred1:ENV1,ENV2;cred2:ENV1,ENV2", + out: map[string]map[string]string{ + "cred1": { + "ENV1": "VALUE1", + "ENV2": "VALUE2", + }, + "cred2": { + "ENV1": "VALUE1", + "ENV2": "VALUE2", + }, + }, + }, + { + name: "multiple creds, key value pairs", + envs: map[string]string{ + "ENV1": "VALUE1", + "ENV2": "VALUE2", + }, + in: "cred1:ENV1=OTHERVALUE1,ENV2=OTHERVALUE2;cred2:ENV1=OTHERVALUE3,ENV2=OTHERVALUE4", + out: map[string]map[string]string{ + "cred1": { + "ENV1": "OTHERVALUE1", + "ENV2": "OTHERVALUE2", + }, + "cred2": { + "ENV1": "OTHERVALUE3", + "ENV2": "OTHERVALUE4", + }, + }, + }, + { + name: "invalid format", + in: "cred1=ENV1,ENV2", + expectErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + envs := tc.envs + if envs == nil { + envs = map[string]string{} + } + + for k, v := range envs { + _ = os.Setenv(k, v) + } + + out, err := parseCredentialOverrides(tc.in) + if tc.expectErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + require.Equal(t, len(tc.out), len(out), "expected %d creds, but got %d", len(tc.out), len(out)) + require.Equal(t, tc.out, out, "expected output %v, but got %v", tc.out, out) + }) + } +}