Skip to content

Commit 982f9ce

Browse files
authored
Merge pull request #2 from Abdul-Moiz31/feat/native-ruby-sdk-v1
Add native Ruby SDK v1 with universal auth and secrets CRUD
2 parents bd333ab + 007f958 commit 982f9ce

30 files changed

Lines changed: 1662 additions & 1 deletion

.github/workflows/release.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
timeout-minutes: 10
12+
permissions:
13+
id-token: write
14+
contents: read
15+
steps:
16+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
17+
18+
- name: Set up Ruby
19+
uses: ruby/setup-ruby@e5517072e87f198d9533967ae13d97c11b604005 # v1.99.0
20+
with:
21+
ruby-version: "3.3"
22+
bundler-cache: true
23+
24+
- name: Verify tag matches Infisical::VERSION
25+
run: |
26+
TAG_VERSION="${GITHUB_REF_NAME#v}"
27+
GEM_VERSION="$(ruby -r ./lib/infisical/version -e 'puts Infisical::VERSION')"
28+
if [ "$TAG_VERSION" != "$GEM_VERSION" ]; then
29+
echo "::error::Tag ${GITHUB_REF_NAME} does not match Infisical::VERSION (${GEM_VERSION})."
30+
echo "::error::Bump lib/infisical/version.rb on main, then re-tag."
31+
exit 1
32+
fi
33+
34+
- name: Run tests
35+
run: bundle exec rspec
36+
37+
- name: Run rubocop
38+
run: bundle exec rubocop
39+
40+
- name: Configure RubyGems credentials (trusted publishing)
41+
uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0
42+
43+
- name: Build gem
44+
run: gem build infisical-sdk.gemspec
45+
46+
- name: Push to RubyGems
47+
run: gem push "infisical-sdk-${GITHUB_REF_NAME#v}.gem"

.github/workflows/test.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Test
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
timeout-minutes: 5
11+
strategy:
12+
matrix:
13+
ruby-version: ["3.0", "3.3"]
14+
steps:
15+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16+
17+
- name: Set up Ruby
18+
uses: ruby/setup-ruby@e5517072e87f198d9533967ae13d97c11b604005 # v1.99.0
19+
with:
20+
ruby-version: ${{ matrix.ruby-version }}
21+
bundler-cache: true
22+
23+
- name: Run tests
24+
run: bundle exec rspec
25+
26+
- name: Run rubocop
27+
run: bundle exec rubocop

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@
66
/pkg/
77
/spec/reports/
88
/spec/examples.txt
9+
/.rspec_status
910
/test/tmp/
1011
/test/version_tmp/
1112
/tmp/
1213

14+
# Library gem: let each environment (and each CI matrix entry) resolve its
15+
# own compatible dependency set rather than pinning one.
16+
/Gemfile.lock
17+
1318
# Used by dotenv library to load environment variables.
1419
# .env
1520

.rspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--require spec_helper
2+
--format documentation
3+
--color

.rubocop.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
AllCops:
2+
TargetRubyVersion: 3.0
3+
NewCops: enable
4+
SuggestExtensions: false
5+
Exclude:
6+
- "bin/**/*"
7+
- "vendor/**/*"
8+
9+
Style/Documentation:
10+
Enabled: false
11+
12+
Naming/FileName:
13+
Exclude:
14+
# Deliberately dashed: it must match the gem name so Bundler.require
15+
# (`gem "infisical-sdk"`) resolves without a `require:` override.
16+
- "lib/infisical-sdk.rb"
17+
18+
Style/StringLiterals:
19+
EnforcedStyle: double_quotes
20+
21+
Metrics/MethodLength:
22+
Max: 25
23+
24+
Metrics/ClassLength:
25+
Max: 150
26+
27+
Metrics/AbcSize:
28+
Max: 25
29+
30+
Metrics/ParameterLists:
31+
# Keyword args are self-documenting at the call site; don't penalize a
32+
# wide options surface the way positional args would be.
33+
CountKeywordArgs: false
34+
35+
Metrics/BlockLength:
36+
Exclude:
37+
- "spec/**/*"

.ruby-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.3.6

.yardopts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--markup markdown
2+
lib/**/*.rb
3+
-
4+
README.md
5+
LICENSE

Gemfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
gemspec
6+
7+
group :development, :test do
8+
gem "rspec", "~> 3.13"
9+
gem "rubocop", "~> 1.65"
10+
gem "webmock", "~> 3.23"
11+
gem "yard", "~> 0.9"
12+
end

README.md

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,134 @@
1-
# ruby-sdk
1+
<h1 align="center">Infisical Ruby SDK</h1>
2+
3+
<h4 align="center">
4+
<a href="https://infisical.com/docs/sdks/languages/ruby">Documentation</a> |
5+
<a href="https://www.infisical.com">Website</a> |
6+
<a href="https://infisical.com/slack">Slack</a>
7+
</h4>
8+
9+
## Introduction
10+
11+
**[Infisical](https://infisical.com)** is the open source secret management platform that teams use to centralize their secrets like API keys, database credentials, and configurations.
12+
13+
This is the official native Ruby SDK — it talks directly to the Infisical REST API and has no Rust/FFI dependency. It replaces the deprecated `infisical-sdk` gem that was built on Infisical's legacy cross-language architecture, so it is **not** a drop-in replacement: the public API differs from the old gem.
14+
15+
## Installation
16+
17+
```ruby
18+
gem "infisical-sdk"
19+
```
20+
21+
## Quick start
22+
23+
```ruby
24+
require "infisical"
25+
26+
client = Infisical::Client.new
27+
28+
client.auth.universal_auth_login(
29+
client_id: ENV.fetch("INFISICAL_CLIENT_ID"),
30+
client_secret: ENV.fetch("INFISICAL_CLIENT_SECRET")
31+
)
32+
33+
secrets = client.secrets.list(
34+
project_id: "<your-project-id>",
35+
environment: "dev"
36+
)
37+
38+
secrets.each { |secret| puts "#{secret.secret_key}=#{secret.secret_value}" }
39+
```
40+
41+
### Authentication
42+
43+
```ruby
44+
# Universal Auth (machine identity client id/secret). Returns the full
45+
# credential, so you can build your own token refresh on top of it.
46+
credential = client.auth.universal_auth_login(client_id: "...", client_secret: "...")
47+
credential.access_token # the token this client now uses
48+
credential.expires_in # seconds until it expires
49+
credential.access_token_max_ttl
50+
credential.token_type # "Bearer"
51+
52+
# Or use a token you already have
53+
client.auth.access_token("existing-access-token")
54+
```
55+
56+
### Secrets
57+
58+
```ruby
59+
client.secrets.list(project_id: "...", environment: "dev", secret_path: "/")
60+
61+
# Imported secrets are folded in by default; pass include_imports: false to
62+
# opt out. Recursive mode collapses duplicate keys across folders to one
63+
# secret per key unless skip_unique_validation: true is passed.
64+
client.secrets.list(project_id: "...", environment: "dev", recursive: true)
65+
66+
# Export fetched secrets into the process environment (never overrides
67+
# variables that already have a value):
68+
client.secrets.list(project_id: "...", environment: "dev", attach_to_process_env: true)
69+
ENV.fetch("DATABASE_URL")
70+
71+
client.secrets.get("DATABASE_URL", project_id: "...", environment: "dev")
72+
client.secrets.create("DATABASE_URL", "postgres://...", project_id: "...", environment: "dev")
73+
client.secrets.update("DATABASE_URL", project_id: "...", environment: "dev", secret_value: "postgres://...")
74+
client.secrets.delete("DATABASE_URL", project_id: "...", environment: "dev")
75+
76+
# create and update accept skip_multiline_encoding: true to disable the
77+
# API's encoding of multi-line values.
78+
```
79+
80+
Each returned secret exposes `secret_key`, `secret_value`, `secret_comment`,
81+
`secret_path`, `version`, `metadata` (an array of key/value entries), and
82+
`tags` (an array with `id`, `slug`, `name`, and `color`).
83+
84+
### Error handling
85+
86+
Every error raised by the SDK inherits from `Infisical::Error`. API failures
87+
raise `Infisical::APIError` (with `status`, `url`, `http_method`, and
88+
`request_id` readers), and well-known statuses raise a dedicated subclass:
89+
90+
```ruby
91+
begin
92+
client.secrets.get("DATABASE_URL", project_id: "...", environment: "dev")
93+
rescue Infisical::NotFoundError # 404
94+
# secret does not exist
95+
rescue Infisical::AuthenticationError # 401: bad or expired credentials
96+
# re-authenticate
97+
rescue Infisical::APIError => e # anything else the API rejected
98+
# e.status, e.url, e.http_method, e.request_id
99+
end
100+
```
101+
102+
Also available: `Infisical::PermissionError` (403), `Infisical::RateLimitError`
103+
(429, raised only after automatic retries are exhausted), and
104+
`Infisical::ServerError` (5xx). Network-level failures (timeouts, DNS,
105+
connection resets) raise `Infisical::RequestError` after retries.
106+
107+
### Self-hosted instances
108+
109+
```ruby
110+
client = Infisical::Client.new(site_url: "https://your-infisical-instance.com")
111+
```
112+
113+
A trailing `/api` on the site URL (as used with some other Infisical SDKs) is
114+
accepted and normalized away.
115+
116+
## Documentation
117+
118+
You can find the documentation for the Ruby SDK on our [SDK documentation page](https://infisical.com/docs/sdks/languages/ruby).
119+
120+
## Development
121+
122+
```bash
123+
bin/setup # bundle install
124+
bundle exec rspec
125+
bundle exec rubocop
126+
```
127+
128+
## Security
129+
130+
Please do not file GitHub issues or post on our public forum for security vulnerabilities, as they are public!
131+
132+
Infisical takes security issues very seriously. If you have any concerns about Infisical or believe you have uncovered a vulnerability, please get in touch via the e-mail address security@infisical.com. In the message, try to provide a description of the issue and ideally a way of reproducing it. The security team will get back to you as soon as possible.
133+
134+
Note that this security address should be used only for undisclosed vulnerabilities. Please report any security problems to us before disclosing it publicly.

Rakefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
require "bundler/gem_tasks"
4+
require "rspec/core/rake_task"
5+
6+
RSpec::Core::RakeTask.new(:spec)
7+
8+
require "rubocop/rake_task"
9+
RuboCop::RakeTask.new
10+
11+
task default: %i[spec rubocop]

0 commit comments

Comments
 (0)