-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Configure apiKey
, additionalHeaders
, and host
through environment variables
#22
Changes from 4 commits
8eac891
8446df9
995829d
6eb91a3
9f56ded
e6ba260
fc13ea1
db00a6d
99e6972
7dea994
2e394d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
API_KEY="<Project API Key>" | ||
PINECONE_API_KEY="<Project API Key>" | ||
TEST_POD_INDEX_NAME="<Pod based Index name>" | ||
TEST_SERVERLESS_INDEX_NAME="<Serverless based Index name>" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,10 @@ import ( | |
"encoding/json" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"strings" | ||
|
||
"github.com/deepmap/oapi-codegen/v2/pkg/securityprovider" | ||
|
@@ -16,25 +19,34 @@ import ( | |
|
||
type Client struct { | ||
apiKey string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is apiKey still needed in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed from |
||
headers map[string]string | ||
restClient *control.Client | ||
sourceTag string | ||
headers map[string]string | ||
} | ||
|
||
type NewClientParams struct { | ||
ApiKey string // required unless Authorization header provided | ||
SourceTag string // optional | ||
Headers map[string]string // optional | ||
Host string // optional | ||
RestClient *http.Client // optional | ||
SourceTag string // optional | ||
} | ||
|
||
func NewClient(in NewClientParams) (*Client, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd expect this func to just call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It now ultimately calls |
||
clientOptions, err := buildClientOptions(in) | ||
clientOptions, err := in.buildClientOptions() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
client, err := control.NewClient("https://api.pinecone.io", clientOptions...) | ||
controlHostOverride := valueOrFallback(in.Host, os.Getenv("PINECONE_CONTROLLER_HOST")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "CONTROL" vs "CONTROLLER"? (Assuming this is talking about the control plane? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went with
I suppose it is a bit confusing given it's applied as an override to the control plane. I think staying consistent makes sense, although the variable name here is confusing. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice job throwing this in for consistency with other clients. |
||
if controlHostOverride != "" { | ||
controlHostOverride, err = ensureHTTP(controlHostOverride) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
client, err := control.NewClient(valueOrFallback(controlHostOverride, "https://api.pinecone.io"), clientOptions...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -421,26 +433,52 @@ func derefOrDefault[T any](ptr *T, defaultValue T) T { | |
return *ptr | ||
} | ||
|
||
func buildClientOptions(in NewClientParams) ([]control.ClientOption, error) { | ||
func (ncp *NewClientParams) buildClientOptions() ([]control.ClientOption, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could buildClientOptions to be a wrapper around buildClientBaseOptions? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I slimmed things down to just |
||
clientOptions := []control.ClientOption{} | ||
hasAuthorizationHeader := false | ||
hasApiKey := in.ApiKey != "" | ||
osApiKey := os.Getenv("PINECONE_API_KEY") | ||
hasApiKey := (ncp.ApiKey != "" || osApiKey != "") | ||
envAdditionalHeaders, hasEnvAdditionalHeaders := os.LookupEnv("PINECONE_ADDITIONAL_HEADERS") | ||
|
||
userAgentProvider := provider.NewHeaderProvider("User-Agent", useragent.BuildUserAgent(in.SourceTag)) | ||
// build and apply user agent | ||
userAgentProvider := provider.NewHeaderProvider("User-Agent", useragent.BuildUserAgent(ncp.SourceTag)) | ||
clientOptions = append(clientOptions, control.WithRequestEditorFn(userAgentProvider.Intercept)) | ||
|
||
for key, value := range in.Headers { | ||
headerProvider := provider.NewHeaderProvider(key, value) | ||
// apply headers from parameters if passed, otherwise use environment headers | ||
if ncp.Headers != nil { | ||
for key, value := range ncp.Headers { | ||
headerProvider := provider.NewHeaderProvider(key, value) | ||
|
||
if strings.Contains(strings.ToLower(key), "authorization") { | ||
hasAuthorizationHeader = true | ||
if strings.Contains(strings.ToLower(key), "authorization") { | ||
hasAuthorizationHeader = true | ||
} | ||
|
||
clientOptions = append(clientOptions, control.WithRequestEditorFn(headerProvider.Intercept)) | ||
} | ||
} else if hasEnvAdditionalHeaders { | ||
additionalHeaders := make(map[string]string) | ||
err := json.Unmarshal([]byte(envAdditionalHeaders), &additionalHeaders) | ||
if err != nil { | ||
log.Printf("failed to parse PINECONE_ADDITIONAL_HEADERS: %v", err) | ||
} else { | ||
for header, value := range additionalHeaders { | ||
headerProvider := provider.NewHeaderProvider(header, value) | ||
|
||
if strings.Contains(strings.ToLower(header), "authorization") { | ||
hasAuthorizationHeader = true | ||
} | ||
|
||
clientOptions = append(clientOptions, control.WithRequestEditorFn(headerProvider.Intercept)) | ||
clientOptions = append(clientOptions, control.WithRequestEditorFn(headerProvider.Intercept)) | ||
} | ||
} | ||
} | ||
|
||
if !hasAuthorizationHeader { | ||
apiKeyProvider, err := securityprovider.NewSecurityProviderApiKey("header", "Api-Key", in.ApiKey) | ||
// if apiKey is provided and no auth header is set, add the apiKey as a header | ||
// apiKey from parameters takes precedence over apiKey from environment | ||
if hasApiKey && !hasAuthorizationHeader { | ||
appliedApiKey := valueOrFallback(ncp.ApiKey, osApiKey) | ||
|
||
apiKeyProvider, err := securityprovider.NewSecurityProviderApiKey("header", "Api-Key", appliedApiKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -451,9 +489,30 @@ func buildClientOptions(in NewClientParams) ([]control.ClientOption, error) { | |
return nil, fmt.Errorf("no API key provided, please pass an API key for authorization") | ||
} | ||
|
||
if in.RestClient != nil { | ||
clientOptions = append(clientOptions, control.WithHTTPClient(in.RestClient)) | ||
if ncp.RestClient != nil { | ||
clientOptions = append(clientOptions, control.WithHTTPClient(ncp.RestClient)) | ||
} | ||
|
||
return clientOptions, nil | ||
} | ||
|
||
func ensureHTTP(inputURL string) (string, error) { | ||
parsedURL, err := url.Parse(inputURL) | ||
if err != nil { | ||
return "", fmt.Errorf("invalid URL: %v", err) | ||
} | ||
|
||
if parsedURL.Scheme == "" { | ||
return "https://" + inputURL, nil | ||
} | ||
return inputURL, nil | ||
} | ||
|
||
func valueOrFallback[T comparable](value, fallback T) T { | ||
var zero T | ||
if value != zero { | ||
return value | ||
} else { | ||
return fallback | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we have two different constructors? One that requires an ApiKey and the other that requires a different auth mechanism (bearer token? oauth token?). It feels clunky that the "ApiKey is required unless..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That does seem reasonable. I'm less clear on what this would look like though since we don't officially offer alternative methods of authentication quite yet. The other clients require an API key for instance.
If the user is able to supply headers directly then they could technically always pass their own auth headers. I think this would also be an issue on Python and TypeScript since they don't do any checking of the headers that are provided. You could also consider this a user-error that they'd need to resolve themselves.
I'm a bit unsure of what would make the most sense here to be honest. 🤔