Go client library for interacting with the Grayskull secret management service.
- Features
- Integration
- Quick Start
- Public APIs
- Configuration
- Authentication
- Metrics
- Logging & Observability
- Error Handling
- Compatibility Matrix
- Building from Source
- License
✅ Simple API - Clean, intuitive interface for retrieving secrets
✅ Automatic Retries - Exponential backoff with jitter for transient failures
✅ Prometheus Metrics - Built-in metrics collection and export
✅ Thread-Safe - Concurrent request handling with connection pooling
✅ Pluggable Auth - Custom authentication provider support
✅ Context Support - Full context.Context integration for cancellation and timeouts
go get github.com/flipkart-incubator/grayskull/clients/go/client-impl
go get github.com/flipkart-incubator/grayskull/clients/go/client-apipackage main
import (
"context"
"fmt"
"log"
client_impl "github.com/flipkart-incubator/grayskull/clients/go/client-impl"
"github.com/flipkart-incubator/grayskull/clients/go/client-impl/auth"
"github.com/flipkart-incubator/grayskull/clients/go/client-impl/models"
)
func main() {
// 1. Configure the client
config := models.NewDefaultConfig()
config.Host = "https://grayskull.example.com"
// 2. Create authentication provider
authProvider, err := auth.NewBasicAuthHeaderProvider("username", "password")
if err != nil {
log.Fatalf("Failed to create auth provider: %v", err)
}
// 3. Initialize the client
client, err := client_impl.NewGrayskullClient(authProvider, config, nil)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
// 4. Retrieve a secret
ctx := context.Background()
secret, err := client.GetSecret(ctx, "my-project:secret-1")
if err != nil {
log.Fatalf("Failed to get secret: %v", err)
}
fmt.Printf("Secret: %s\n", secret.PublicPart)
}The main interface for interacting with the Grayskull service.
type Client interface {
GetSecret(ctx context.Context, secretRef string) (*models.SecretValue, error)
RegisterRefreshHook(ctx context.Context, secretRef string, hook hooks.SecretRefreshHook) (hooks.RefreshHandlerRef, error)
}Retrieves a secret from the Grayskull server.
Parameters:
ctx- Context for request cancellation, timeout, and tracingsecretRef- Secret reference in format"projectId:secretName"(e.g.,"my-project:database-password")
Returns:
*models.SecretValue- Object containing the secret's public part, private part, and versionerror- Error if retrieval fails or retries are exhausted
Example:
// Simple retrieval
ctx := context.Background()
secret, err := client.GetSecret(ctx, "prod-app:api-key")
if err != nil {
log.Fatalf("Failed to get secret: %v", err)
}
// Access secret components
publicPart := secret.PublicPart // e.g., "username" or public data
privatePart := secret.PrivatePart // e.g., "password" or sensitive data
version := secret.DataVersion // e.g., 5With Timeout:
import (
"context"
"time"
)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
secret, err := client.GetSecret(ctx, "my-project:api-key")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
log.Fatal("Request timed out")
}
log.Fatalf("Error: %v", err)
}Registers a callback to be invoked when a secret is updated.
Parameters:
ctx- Context for the operationsecretRef- Secret reference to monitorhook- Callback function to execute on updates
Returns:
RefreshHandlerRef- Handle for managing the hook lifecycleerror- Error if registration fails
Note:
Example:
import (
"github.com/flipkart-incubator/grayskull/clients/go/client-api/hooks"
"github.com/flipkart-incubator/grayskull/clients/go/client-api/models"
)
// Define a refresh hook function
hook := func(secret models.SecretValue) error {
fmt.Printf("Secret updated! New version: %d\n", secret.DataVersion)
return updateCache(secret)
}
// Register the hook
handle, err := client.RegisterRefreshHook(
context.Background(),
"my-project:api-key",
hook,
)
if err != nil {
log.Fatalf("Failed to register hook: %v", err)
}
// Later: unregister the hook
handle.Unregister()All configuration properties with their defaults and constraints.
| Property | Type | Default | Range/Format | Description |
|---|---|---|---|---|
Host |
string |
"" (empty) |
URL | Grayskull server endpoint (e.g., "https://grayskull.example.com") - Required |
ConnectionTimeout |
int |
10000 |
≥ 0 ms | Max time to establish connection (10 seconds) |
ReadTimeout |
int |
30000 |
≥ 0 ms | Max time to wait for response data (30 seconds) |
MaxConnections |
int |
10 |
> 0 | Connection pool size |
IdleConnTimeout |
int |
300000 |
≥ 0 ms | Max time idle connection remains open (5 minutes) |
MaxIdleConns |
int |
10 |
≥ 0 | Max idle connections across all hosts |
MaxIdleConnsPerHost |
int |
10 |
≥ 0 | Max idle connections per host |
MaxRetries |
int |
3 |
≥ 0 | Number of retry attempts for transient failures |
MinRetryDelay |
int |
100 |
≥ 0 ms | Base delay between retries (exponential backoff) |
MetricsEnabled |
bool |
true |
true/false | Enable/disable metrics collection |
Example:
config := models.NewDefaultConfig()
config.Host = "https://grayskull.example.com"
config.ConnectionTimeout = 10000 // 10 seconds
config.ReadTimeout = 30000 // 30 seconds
config.MaxConnections = 10
config.IdleConnTimeout = 300000 // 5 minutes
config.MaxIdleConns = 10
config.MaxIdleConnsPerHost = 10
config.MaxRetries = 3
config.MinRetryDelay = 100 // 100ms
config.MetricsEnabled = trueThe client uses pluggable authentication via the GrayskullAuthHeaderProvider interface.
authProvider, err := auth.NewBasicAuthHeaderProvider("username", "password")
if err != nil {
log.Fatalf("Failed to create auth provider: %v", err)
}Note: NewBasicAuthHeaderProvider returns an error if username or password is empty.
Generates HTTP Basic Auth headers: Basic base64(username:password)
Implement GrayskullAuthHeaderProvider for custom auth schemes (JWT, API keys, etc.):
type CustomAuthProvider struct {
token string
}
func (c *CustomAuthProvider) GetAuthHeader() (string, error) {
return "Bearer " + c.token, nil
}Note:
Thread Safety: Implementations must be thread-safe as GetAuthHeader() is called concurrently.
The SDK provides comprehensive observability with Prometheus metrics.
When metrics are enabled, the client exposes metrics to the Prometheus default registry.
Type: Histogram
Description: Duration of HTTP requests in seconds
Labels:
name(string): The operation name (e.g., "get_secret")status_code(string): The HTTP status code of the response (e.g., "200", "404", "500")
Buckets: Uses Prometheus default buckets (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10)
Example Queries:
# Average request duration by operation
rate(grayskull_http_client_request_duration_seconds_sum[5m]) / rate(grayskull_http_client_request_duration_seconds_count[5m])
# P95 latency for get_secret operations
histogram_quantile(0.95, rate(grayskull_http_client_request_duration_seconds_bucket{name="get_secret"}[5m]))
# Request count by status code
sum by (status_code) (rate(grayskull_http_client_request_duration_seconds_count[5m]))
Type: Counter
Description: Total number of retry attempts
Labels:
url(string): The URL being retriedsuccess(string): Whether the retry was successful ("true" or "false")
Example Queries:
# Total retry rate
rate(grayskull_http_client_retry_attempts_total[5m])
# Failed retry rate
rate(grayskull_http_client_retry_attempts_total{success="false"}[5m])
# Retry success rate
sum(rate(grayskull_http_client_retry_attempts_total{success="true"}[5m])) / sum(rate(grayskull_http_client_retry_attempts_total[5m]))
import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
)
// Expose metrics endpoint
http.Handle("/metrics", promhttp.Handler())
go http.ListenAndServe(":9090", nil)
// Now Grayskull client metrics are available at http://localhost:9090/metricsconfig.MetricsEnabled = false // No metrics overheadThe SDK uses Go's standard log/slog for structured logging.
The client automatically adds context to logs for all operations:
| Context Key | Description | Example Value |
|---|---|---|
grayskullRequestId |
Unique identifier for each request | "abc-123-def-456" |
projectId |
Grayskull project ID | "my-project" |
secretName |
Name of the secret being accessed | "database-password" |
The SDK automatically includes the X-Request-Id header in all HTTP requests to the Grayskull server. This enables end-to-end request correlation:
Client Log: [grayskullRequestId:abc-123] Fetching secret
HTTP Header: X-Request-Id: abc-123
Server Log: [RequestId:abc-123] Processing secret request
This makes it easy to trace a single request through your entire system, from client to server and back.
The SDK provides the following error types:
1. BaseError (struct)
- Provides common error functionality (status code, message, cause)
- Implements the standard Go
errorinterface - Supports error unwrapping via
Unwrap()method
2. GrayskullError (embeds BaseError)
- Main error type returned by client operations
- Includes HTTP status code and error message
- Publicly exposed for error handling
3. RetryableError (embeds BaseError)
- Internal error type for transient failures
- Used internally by retry logic
- Not directly exposed to users
Error Structure:
type BaseError struct {
statusCode int
message string
cause error
}
type GrayskullError struct {
BaseError // embedded
}
type RetryableError struct {
BaseError // embedded
}The main error type returned by the client.
import (
"errors"
grayskullErrors "github.com/flipkart-incubator/grayskull/clients/go/client-impl/models/errors"
)
secret, err := client.GetSecret(ctx, "project:secret")
if err != nil {
var grayskullErr *grayskullErrors.GrayskullError
if errors.As(err, &grayskullErr) {
fmt.Printf("Grayskull error (status %d): %v\n", grayskullErr.StatusCode(), grayskullErr)
return
}
log.Fatalf("Unexpected error: %v", err)
}The client automatically retries transient failures using exponential backoff with jitter.
Retryable Conditions:
- HTTP 5xx errors
- HTTP 408 Request Timeout
- HTTP 429 Too Many Requests
- Network errors
Non-Retryable Conditions:
- HTTP 4xx errors (except 408 and 429)
- Invalid configuration
- Invalid secret reference format
| Module | Version |
|---|---|
client-api |
0.2.0 |
client-impl |
0.2.0 |
| Dependency | Version | Required |
|---|---|---|
| Go Runtime | 1.21+ | ✅ Yes |
| Prometheus Client | v1.23.2+ | ✅ Yes |
| Google UUID | v1.6.0+ | ✅ Yes |
| go-playground/validator | v10.24.0+ | ✅ Yes |
- Go 1.21 or higher
# Clone the repository
git clone https://github.com/flipkart-incubator/grayskull.git
cd grayskull/clients/go
# Build the module
go build ./...
# Run tests
go test ./... -v
# Run tests with coverage
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.outThis project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Copyright 2025 Flipkart
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
For issues, questions, or contributions, please visit the GitHub repository.