# Ruby SDK

Official Ruby SDK for KeyEnv.

Source: https://keyenv.dev/docs/sdks/ruby/

Official Ruby SDK for KeyEnv with Ruby 3.0+ support.

> **Warning**
>
>   **Beta Release**: This SDK is currently in development. APIs may change before the stable 1.0 release.

## Installation

Add to your Gemfile:

```ruby
gem 'keyenv'
```

Then run:

```bash
bundle install
```

Or install directly:

```bash
gem install keyenv
```

## Quick Start

```ruby
require 'keyenv'

client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN'])

# Load secrets into ENV
count = client.load_env(project_id: 'your-project-id', environment: 'production')
puts "Loaded #{count} secrets"

# Access secrets
puts ENV['DATABASE_URL']
```

## Initialize the Client

### Simple Initialization

```ruby
require 'keyenv'

client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN'])
```

### With Options

```ruby
client = KeyEnv::Client.new(
  token: ENV['KEYENV_TOKEN'],
  timeout: 60,      # Request timeout in seconds (default: 30)
  cache_ttl: 300    # Cache TTL in seconds (default: 0 = disabled)
)
```

## Loading Secrets

### Load into ENV

The simplest way to use secrets in your application:

```ruby
count = client.load_env(project_id: 'project-id', environment: 'production')
puts "Loaded #{count} secrets"

# Access via ENV
puts ENV['DATABASE_URL']
puts ENV['API_KEY']
```

### Get Secrets as Hash

Get secrets as a key-value hash:

```ruby
secrets = client.export_secrets_as_hash(project_id: 'project-id', environment: 'production')
puts secrets['DATABASE_URL']
puts secrets['API_KEY']
```

### Export as Array

Get secrets with metadata:

```ruby
secrets = client.export_secrets(project_id: 'project-id', environment: 'production')
secrets.each do |secret|
  puts "#{secret.key}=#{secret.value}"
end
```

## Managing Secrets

### Get a Single Secret

```ruby
secret = client.get_secret(project_id: 'project-id', environment: 'production', key: 'DATABASE_URL')
puts secret.value
puts secret.description
```

### Set a Secret

Creates or updates a secret:

```ruby
client.set_secret(
  project_id: 'project-id',
  environment: 'production',
  key: 'API_KEY',
  value: 'sk_live_...'
)

# With description
client.set_secret(
  project_id: 'project-id',
  environment: 'production',
  key: 'API_KEY',
  value: 'sk_live_...',
  description: 'Production API key'
)
```

### Create a Secret

Explicitly create a new secret:

```ruby
secret = client.create_secret(
  project_id: 'project-id',
  environment: 'production',
  key: 'NEW_KEY',
  value: 'new_value',
  description: 'New secret'
)
```

### Update a Secret

Explicitly update an existing secret:

```ruby
secret = client.update_secret(
  project_id: 'project-id',
  environment: 'production',
  key: 'EXISTING_KEY',
  value: 'updated_value'
)
```

### Delete a Secret

```ruby
client.delete_secret(project_id: 'project-id', environment: 'production', key: 'OLD_KEY')
```

## Bulk Operations

### Bulk Import

Import multiple secrets at once:

```ruby
result = client.bulk_import(
  project_id: 'project-id',
  environment: 'development',
  secrets: [
    { key: 'DATABASE_URL', value: 'postgres://localhost/mydb' },
    { key: 'REDIS_URL', value: 'redis://localhost:6379' },
    { key: 'API_KEY', value: 'sk_test_...', description: 'Test API key' }
  ],
  overwrite: true
)

puts "Created: #{result.created}, Updated: #{result.updated}"
```

### Generate .env File

```ruby
env_content = client.generate_env_file(project_id: 'project-id', environment: 'production')
File.write('.env', env_content)
```

## Projects & Environments

### List Projects

```ruby
projects = client.list_projects
projects.each do |project|
  puts "#{project.name} (#{project.id})"
end
```

### Get Project Details

```ruby
project = client.get_project(project_id: 'project-id')
puts "Project: #{project.name}"
project.environments.each do |env|
  puts "  - #{env.name}"
end
```

### List Environments

```ruby
environments = client.list_environments(project_id: 'project-id')
environments.each do |env|
  puts env.name
end
```

## Environment Permissions

### List Permissions

```ruby
permissions = client.list_permissions(project_id: 'project-id', environment: 'production')
permissions.each do |perm|
  puts "#{perm.user_email}: #{perm.role}"
end
```

### Set Permission

```ruby
client.set_permission(
  project_id: 'project-id',
  environment: 'production',
  user_id: 'user-id',
  role: 'write'
)
```

### Get My Permissions

```ruby
permissions, is_team_admin = client.get_my_permissions(project_id: 'project-id')
permissions.each do |perm|
  puts "#{perm.environment_name}: #{perm.role} (can_write: #{perm.can_write})"
end
```

## Caching

Enable caching for better performance in high-throughput applications:

```ruby
client = KeyEnv::Client.new(
  token: ENV['KEYENV_TOKEN'],
  cache_ttl: 300  # 5 minutes
)

# First call fetches from API
client.export_secrets(project_id: 'project-id', environment: 'production')

# Subsequent calls within TTL use cache
client.export_secrets(project_id: 'project-id', environment: 'production')

# Clear cache when needed
client.clear_cache(project_id: 'project-id', environment: 'production')
client.clear_cache  # Clear all
```

## Error Handling

```ruby
require 'keyenv'

begin
  secret = client.get_secret(project_id: 'project-id', environment: 'production', key: 'MISSING_KEY')
rescue KeyEnv::AuthenticationError => e
  puts "Invalid or expired token: #{e.message}"
rescue KeyEnv::NotFoundError => e
  puts "Secret not found: #{e.message}"
rescue KeyEnv::ValidationError => e
  puts "Validation error: #{e.message}"
  puts "Details: #{e.details}" if e.details
rescue KeyEnv::RateLimitError => e
  puts "Rate limited, try again later"
rescue KeyEnv::Error => e
  puts "Error #{e.status}: #{e.message}"
end
```

## API Reference

### Constructor Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `token` | `String` | Yes | - | Service token |
| `timeout` | `Integer` | No | `30` | Request timeout (seconds) |
| `cache_ttl` | `Integer` | No | `0` | Cache TTL (0 disables) |

### Methods

| Method | Description |
|--------|-------------|
| `get_current_user` | Get current user/token info |
| `validate_token` | Validate token and get user info |
| `list_projects` | List all accessible projects |
| `get_project(project_id:)` | Get project with environments |
| `list_environments(project_id:)` | List environments in a project |
| `list_secrets(project_id:, environment:)` | List secret keys (no values) |
| `export_secrets(project_id:, environment:)` | Export secrets with values |
| `export_secrets_as_hash(project_id:, environment:)` | Export as hash |
| `get_secret(project_id:, environment:, key:)` | Get single secret |
| `create_secret(project_id:, environment:, key:, value:)` | Create new secret |
| `update_secret(project_id:, environment:, key:, value:)` | Update existing secret |
| `set_secret(project_id:, environment:, key:, value:)` | Create or update secret |
| `delete_secret(project_id:, environment:, key:)` | Delete secret |
| `bulk_import(project_id:, environment:, secrets:)` | Bulk import secrets |
| `load_env(project_id:, environment:)` | Load secrets into ENV |
| `generate_env_file(project_id:, environment:)` | Generate .env file content |
| `list_permissions(project_id:, environment:)` | List permissions |
| `set_permission(project_id:, environment:, user_id:, role:)` | Set permission |
| `delete_permission(project_id:, environment:, user_id:)` | Delete permission |
| `get_my_permissions(project_id:)` | Get current user's permissions |
| `get_project_defaults(project_id:)` | Get default permissions |
| `set_project_defaults(project_id:, defaults:)` | Set default permissions |
| `clear_cache` | Clear secrets cache |

## Examples

### Rails Application

```ruby
# config/initializers/keyenv.rb

if ENV['KEYENV_TOKEN'].present?
  client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN'])
  client.load_env(
    project_id: ENV['KEYENV_PROJECT'],
    environment: Rails.env
  )
end
```

### Rails with Credentials

```ruby
# config/initializers/keyenv.rb

if Rails.application.credentials.keyenv&.token
  client = KeyEnv::Client.new(token: Rails.application.credentials.keyenv.token)
  client.load_env(
    project_id: Rails.application.credentials.keyenv.project,
    environment: Rails.env
  )
end
```

### Sinatra Application

```ruby
require 'sinatra'
require 'keyenv'

# Load secrets at startup
if ENV['KEYENV_TOKEN']
  client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN'])
  client.load_env(project_id: ENV['KEYENV_PROJECT'], environment: 'production')
end

get '/' do
  { status: 'ok' }.to_json
end

get '/config' do
  content_type :json
  {
    api_url: ENV['API_URL']
    # Don't expose sensitive secrets!
  }.to_json
end
```

### Sidekiq Worker

```ruby
# config/initializers/sidekiq.rb

Sidekiq.configure_server do |config|
  if ENV['KEYENV_TOKEN']
    client = KeyEnv::Client.new(
      token: ENV['KEYENV_TOKEN'],
      cache_ttl: 300  # Cache for 5 minutes
    )
    client.load_env(project_id: ENV['KEYENV_PROJECT'], environment: Rails.env)
  end
end
```

### Rake Task

```ruby
# lib/tasks/secrets.rake

namespace :secrets do
  desc 'Sync secrets from KeyEnv to .env file'
  task :sync do
    require 'keyenv'

    token = ENV['KEYENV_TOKEN'] || raise('KEYENV_TOKEN not set')
    project = ENV['KEYENV_PROJECT'] || raise('KEYENV_PROJECT not set')
    env = ENV['RAILS_ENV'] || 'development'

    client = KeyEnv::Client.new(token: token)
    content = client.generate_env_file(project_id: project, environment: env)

    File.write('.env', content)
    puts "Synced secrets to .env"
  end
end
```

### RSpec Test Setup

```ruby
# spec/support/keyenv.rb

RSpec.configure do |config|
  config.before(:suite) do
    if ENV['KEYENV_TOKEN']
      client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN'])
      client.load_env(project_id: ENV['KEYENV_PROJECT'], environment: 'test')
    end
  end
end
```

### AWS Lambda with Ruby

```ruby
require 'keyenv'

# Initialize client outside handler for reuse across invocations
$keyenv_client = KeyEnv::Client.new(
  token: ENV['KEYENV_TOKEN'],
  cache_ttl: 300  # Cache for 5 minutes in warm lambdas
)

def handler(event:, context:)
  # Load secrets (cached after first invocation)
  $keyenv_client.load_env(
    project_id: ENV['KEYENV_PROJECT'],
    environment: 'production'
  )

  # Your handler logic here
  {
    statusCode: 200,
    body: JSON.generate({ message: 'Success' })
  }
end
```
