An open API service indexing awesome lists of open source software.

https://github.com/lambdalisue/rs-serviceconf

Environment variable configuration with file-based secrets support
https://github.com/lambdalisue/rs-serviceconf

docker env kubenetes rust

Last synced: 4 months ago
JSON representation

Environment variable configuration with file-based secrets support

Awesome Lists containing this project

README

          

# serviceconf

[![Build](https://github.com/lambdalisue/rs-serviceconf/actions/workflows/build.yml/badge.svg)](https://github.com/lambdalisue/rs-serviceconf/actions/workflows/build.yml)
[![Crates.io Version](https://img.shields.io/crates/v/serviceconf)](https://crates.io/crates/serviceconf)
[![docs.rs](https://img.shields.io/docsrs/serviceconf)](https://docs.rs/serviceconf/0.1.0/serviceconf/)

**Environment variable configuration with file-based secrets support**

Load configuration from environment variables with native support for **file-based secrets** (Kubernetes Secrets, Docker Secrets). This is the primary feature that distinguishes `serviceconf` from other environment variable configuration libraries.

## Key Feature: File-based Secrets

The `#[conf(from_file)]` attribute allows reading secrets from files mounted by Kubernetes or Docker, avoiding the security risks of exposing secrets directly in environment variables:

```rust
use serviceconf::ServiceConf;

#[derive(ServiceConf)]
struct Config {
#[conf(from_file)]
pub api_key: String, // Reads from API_KEY or API_KEY_FILE

#[conf(from_file)]
pub database_password: String,
}
```

**Why file-based secrets?**
- ✅ **More secure**: Secrets stored in files, not environment variables (which can leak in logs, process lists, etc.)
- ✅ **Kubernetes native**: Works seamlessly with Kubernetes Secrets mounting
- ✅ **Docker Secrets**: Direct support for Docker Swarm secrets
- ✅ **Flexible**: Falls back to direct environment variables for local development

**Loading priority:**
1. Direct env var (`API_KEY`) - for local development
2. File path from env var (`API_KEY_FILE`) - for production

**Kubernetes Secret example:**

```yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
api-key: "prod-api-key-123"
db-password: "secure-password"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice
spec:
template:
spec:
containers:
- name: app
image: myservice:latest
env:
- name: API_KEY_FILE
value: /etc/secrets/api-key
- name: DATABASE_PASSWORD_FILE
value: /etc/secrets/db-password
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: app-secrets
items:
- key: api-key
path: api-key
- key: db-password
path: db-password
```

**Local development** (no files needed):
```bash
export API_KEY=dev-key-123
export DATABASE_PASSWORD=dev-password
```

## Installation

```toml
[dependencies]
serviceconf = "0.2"
# Or from GitHub
#serviceconf = { git = "https://github.com/lambdalisue/rs-serviceconf" }
```

## Quick Start

```rust
use serviceconf::ServiceConf;

#[derive(Debug, ServiceConf)]
struct Config {
#[conf(from_file)]
pub api_key: String,

#[conf(default = 8080)]
pub port: u16,
}

fn main() {
let config = Config::from_env().unwrap();
println!("Port: {}", config.port);
}
```

**Local development** (direct environment variable):
```bash
export API_KEY=dev-key-123
export PORT=3000
```

**Production** (Kubernetes/Docker with file-based secret):
```bash
export API_KEY_FILE=/run/secrets/api-key
export PORT=8080
```

## Other Features

### Default Values

Use `#[conf(default)]` for `Default::default()` or `#[conf(default = value)]` for explicit values.

```rust
#[derive(ServiceConf)]
struct Config {
#[conf(default = 8080)]
pub port: u16, // 8080 if PORT not set

#[conf(default = "localhost".to_string())]
pub host: String, // "localhost" if HOST not set
}
```

### Optional Fields

Use `Option` for optional fields. Returns `None` if not set.

```rust
#[derive(ServiceConf)]
struct Config {
pub api_key: Option, // None if API_KEY not set
}
```

### Prefix

Use `#[conf(prefix = "...")]` at struct level to prefix all environment variables.

```rust
#[derive(ServiceConf)]
#[conf(prefix = "MYAPP_")]
struct Config {
pub database_url: String, // Reads from MYAPP_DATABASE_URL
pub api_key: String, // Reads from MYAPP_API_KEY
}
```

```bash
export MYAPP_DATABASE_URL=postgres://localhost/db
export MYAPP_API_KEY=secret123
```

### Custom Environment Variable Names

Use `#[conf(name = "...")]` to override the auto-generated name.

```rust
#[derive(ServiceConf)]
struct Config {
#[conf(name = "POSTGRES_URL")]
pub database_url: String, // Reads from POSTGRES_URL, not DATABASE_URL
}
```

### Custom Deserializers

Use `#[conf(deserializer = "function")]` for complex types or custom parsing.

```rust
// Custom parser
fn comma_separated(s: &str) -> Result, String> {
Ok(s.split(',').map(|s| s.trim().to_string()).collect())
}

#[derive(ServiceConf)]
struct Config {
// JSON array
#[conf(deserializer = "serde_json::from_str")]
pub tags: Vec,

// Comma-separated
#[conf(deserializer = "comma_separated")]
pub features: Vec,

// TOML (requires toml crate)
#[conf(deserializer = "toml::from_str")]
pub settings: MySettings,
}
```

```bash
export TAGS='["prod","api","v2"]'
export FEATURES=feature1,feature2,feature3
```

## Attribute Reference

### Struct-level Attributes

| Attribute | Description |
| ---------------------------- | -------------------------------------------- |
| `#[conf(prefix = "PREFIX_")]` | Add prefix to all environment variable names |

### Field-level Attributes

| Attribute | Description | When to Use |
| ----------------------------- | ----------------------------------- | -------------------------------------------- |
| `#[conf(name = "VAR")]` | Override environment variable name | When field name differs from desired env var |
| `#[conf(default)]` | Use `Default::default()` if not set | For optional fields with sensible defaults |
| `#[conf(default = value)]` | Use explicit default value | When you need a specific default |
| `#[conf(from_file)]` | Support `{VAR}_FILE` pattern | For secrets stored in files |
| `#[conf(deserializer = "fn")]` | Use custom parser | For complex types (Vec, HashMap, etc.) |

### Type Behavior

| Type | When Env Var Missing | When Env Var Set |
| ----------------------------------- | -------------------- | --------------------------- |
| `T` (no attribute) | Error | Parsed with `FromStr` |
| `T` + `#[conf(default)]` | `Default::default()` | Parsed with `FromStr` |
| `T` + `#[conf(default = value)]` | Uses `value` | Parsed with `FromStr` |
| `Option` | `None` | `Some(parsed_value)` |
| `T` + `#[conf(deserializer = "fn")]` | Error | Parsed with custom function |

## Combining Attributes

Multiple attributes can be combined:

```rust
#[derive(ServiceConf)]
#[conf(prefix = "APP_")]
struct Config {
// Combines: prefix + custom name + from_file + Option
#[conf(name = "DB_URL")]
#[conf(from_file)]
pub database_url: Option, // Reads from APP_DB_URL or APP_DB_URL_FILE

// Combines: prefix + default
#[conf(default = 8080)]
pub port: u16, // Reads from APP_PORT, defaults to 8080
}
```

**Invalid combinations** (compile errors):

- `Option` + `#[conf(default)]` or `#[conf(default = value)]` → Option already defaults to None

## Examples

See the [`examples/`](examples/) directory for complete working examples:

| Example | Features Demonstrated |
| --------------------------------------------------------------- | ------------------------------------------------- |
| [`basic.rs`](examples/basic.rs) | Required fields, explicit default values |
| [`optional_fields.rs`](examples/optional_fields.rs) | `Option` for optional fields |
| [`default_trait.rs`](examples/default_trait.rs) | `#[conf(default)]` using `Default` trait |
| [`prefix.rs`](examples/prefix.rs) | `#[conf(prefix = "...")]` at struct level |
| [`file_based_secrets.rs`](examples/file_based_secrets.rs) | `#[conf(from_file)]` for Kubernetes/Docker secrets |
| [`custom_names.rs`](examples/custom_names.rs) | `#[conf(name = "...")]` for custom env var names |
| [`complex_types.rs`](examples/complex_types.rs) | `Vec`, `HashMap` with JSON deserializer |
| [`custom_deserialize_fn.rs`](examples/custom_deserialize_fn.rs) | Custom deserializer functions |
| [`comprehensive.rs`](examples/comprehensive.rs) | Multiple features combined |

Run with: `cargo run --example `

## Error Handling

```rust
match Config::from_env() {
Ok(config) => println!("Config: {:?}", config),
Err(e) => eprintln!("Error: {}", e),
}
```

Example errors:

- `Environment variable 'DATABASE_URL' is required but not set`
- `Failed to parse environment variable 'PORT' as u16: invalid digit found in string`
- `Failed to read file '/etc/secrets/key' for environment variable 'API_KEY_FILE': No such file or directory`

## Testing

```bash
cargo test
```

## License

Licensed under MIT license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT).

## Contribution

Contributions are welcome! Please feel free to submit a Pull Request.