Skip to content

Commit d692ddc

Browse files
authored
feature: secrets (#15)
* Initial Secrets Features * Secrets Core Code * Formatted Files * Unit Tests and Example Usage for Secrets * Format updates on recent changes * Update to v0.3.1
1 parent e2afe65 commit d692ddc

File tree

9 files changed

+1160
-4
lines changed

9 files changed

+1160
-4
lines changed

CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cmake_minimum_required(VERSION 3.14)
22
project(databricks_sdk
3-
VERSION 0.3.0
3+
VERSION 0.3.1
44
DESCRIPTION "Databricks C++ SDK"
55
LANGUAGES CXX)
66

@@ -126,6 +126,7 @@ set(SOURCES
126126
src/connection_pool.cpp
127127
src/unity_catalog/unity_catalog_types.cpp
128128
src/unity_catalog/unity_catalog.cpp
129+
src/secrets/secrets.cpp
129130
src/internal/pool_manager.cpp
130131
src/internal/logger.cpp
131132
src/internal/http_client.cpp
@@ -142,6 +143,8 @@ set(HEADERS
142143
include/databricks/compute/compute_types.h
143144
include/databricks/unity_catalog/unity_catalog.h
144145
include/databricks/unity_catalog/unity_catalog_types.h
146+
include/databricks/secrets/secrets.h
147+
include/databricks/secrets/secrets_types.h
145148
)
146149

147150
# Internal headers (not installed)

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ test: build-tests
6060
.PHONY: test-new
6161
test-new: build-tests
6262
@echo "Running tests for modified files..."
63-
@MODIFIED_FILES=$$(git status --short | grep -E '^\s*M.*\.(cpp|h)' | awk '{print $$2}' | grep -E 'test|jobs|compute|unity' || true); \
63+
@MODIFIED_FILES=$$(git status --short | grep -E '^\s*M.*\.(cpp|h)' | awk '{print $$2}' | grep -E 'test|jobs|compute|unity|secrets' || true); \
6464
if [ -z "$$MODIFIED_FILES" ]; then \
6565
echo "No modified test files detected. Running all tests..."; \
6666
cd $(BUILD_DIR)/tests && ./unit_tests; \
@@ -75,6 +75,9 @@ test-new: build-tests
7575
elif echo "$$MODIFIED_FILES" | grep -q "unity"; then \
7676
echo "Running Unity Catalog tests..."; \
7777
cd $(BUILD_DIR)/tests && ./unit_tests --gtest_filter='*UnityCatalog*'; \
78+
elif echo "$$MODIFIED_FILES" | grep -q "secrets"; then \
79+
echo "Running Secrets tests..."; \
80+
cd $(BUILD_DIR)/tests && ./unit_tests --gtest_filter='*Secret*'; \
7881
else \
7982
echo "Running all tests for safety..."; \
8083
cd $(BUILD_DIR)/tests && ./unit_tests; \

examples/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ target_link_libraries(compute_example PRIVATE databricks_sdk)
1616
add_executable(unity_catalog_example unity_catalog_example.cpp)
1717
target_link_libraries(unity_catalog_example PRIVATE databricks_sdk)
1818

19+
# ========== Secrets API Examples ==========
20+
add_executable(secrets_example secrets_example.cpp)
21+
target_link_libraries(secrets_example PRIVATE databricks_sdk)
22+
1923
# Set RPATH for all examples to find ODBC libraries
2024
set_target_properties(
2125
simple_query
2226
jobs_example
2327
compute_example
2428
unity_catalog_example
29+
secrets_example
2530
PROPERTIES
2631
BUILD_RPATH "${CMAKE_BINARY_DIR};/opt/homebrew/lib;/usr/local/lib"
2732
INSTALL_RPATH "/opt/homebrew/lib;/usr/local/lib"

examples/secrets_example.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright (c) 2025 Calvin Min
2+
// SPDX-License-Identifier: MIT
3+
/**
4+
* @file secrets_example.cpp
5+
* @brief Example demonstrating the Databricks Secrets API
6+
*
7+
* This example shows how to:
8+
* 1. List all secret scopes in your workspace
9+
* 2. Create a new secret scope
10+
* 3. Store a secret in the scope
11+
* 4. List secrets in the scope
12+
* 5. Delete the secret and scope
13+
*/
14+
15+
#include "databricks/core/config.h"
16+
#include "databricks/secrets/secrets.h"
17+
18+
#include <exception>
19+
#include <iostream>
20+
21+
int main() {
22+
try {
23+
// Load configuration from environment
24+
databricks::AuthConfig auth = databricks::AuthConfig::from_environment();
25+
26+
std::cout << "Connecting to: " << auth.host << std::endl;
27+
std::cout << "======================================\n" << std::endl;
28+
29+
// Create Secrets API client
30+
databricks::Secrets secrets(auth);
31+
32+
// ===================================================================
33+
// Example 1: List all secret scopes
34+
// ===================================================================
35+
std::cout << "1. Listing all secret scopes:" << std::endl;
36+
std::cout << "-----------------------------" << std::endl;
37+
38+
auto scopes = secrets.list_scopes();
39+
std::cout << "Found " << scopes.size() << " secret scopes:\n" << std::endl;
40+
41+
for (const auto& scope : scopes) {
42+
std::cout << " Scope Name: " << scope.name << std::endl;
43+
std::cout << " Backend Type: ";
44+
if (scope.backend_type == databricks::SecretScopeBackendType::DATABRICKS) {
45+
std::cout << "DATABRICKS" << std::endl;
46+
} else if (scope.backend_type == databricks::SecretScopeBackendType::AZURE_KEYVAULT) {
47+
std::cout << "AZURE_KEYVAULT" << std::endl;
48+
if (!scope.resource_id.empty()) {
49+
std::cout << " Resource ID: " << scope.resource_id << std::endl;
50+
}
51+
if (!scope.dns_name.empty()) {
52+
std::cout << " DNS Name: " << scope.dns_name << std::endl;
53+
}
54+
} else {
55+
std::cout << "UNKNOWN" << std::endl;
56+
}
57+
std::cout << std::endl;
58+
}
59+
60+
// ===================================================================
61+
// Example 2: Create a new secret scope
62+
// ===================================================================
63+
std::cout << "\n2. Creating a new secret scope:" << std::endl;
64+
std::cout << "--------------------------------" << std::endl;
65+
66+
const std::string example_scope = "example_scope";
67+
std::cout << "Creating scope: " << example_scope << std::endl;
68+
69+
// Create scope with "users" as initial_manage_principal
70+
// This grants MANAGE permission to all workspace users
71+
secrets.create_scope(example_scope, "users", databricks::SecretScopeBackendType::DATABRICKS);
72+
73+
std::cout << "Scope created successfully!\n" << std::endl;
74+
75+
// ===================================================================
76+
// Example 3: Store a secret
77+
// ===================================================================
78+
std::cout << "\n3. Storing a secret:" << std::endl;
79+
std::cout << "--------------------" << std::endl;
80+
81+
const std::string secret_key = "api_key";
82+
const std::string secret_value = "my_secret_value_123";
83+
84+
std::cout << "Storing secret with key: " << secret_key << std::endl;
85+
secrets.put_secret(example_scope, secret_key, secret_value);
86+
std::cout << "Secret stored successfully!\n" << std::endl;
87+
88+
// ===================================================================
89+
// Example 4: List secrets in the scope
90+
// ===================================================================
91+
std::cout << "\n4. Listing secrets in scope '" << example_scope << "':" << std::endl;
92+
std::cout << "-----------------------------------------------" << std::endl;
93+
94+
auto secret_list = secrets.list_secrets(example_scope);
95+
std::cout << "Found " << secret_list.size() << " secrets:\n" << std::endl;
96+
97+
for (const auto& secret : secret_list) {
98+
std::cout << " Key: " << secret.key << std::endl;
99+
std::cout << " Last Updated: " << secret.last_updated_timestamp << std::endl;
100+
std::cout << std::endl;
101+
}
102+
103+
// ===================================================================
104+
// Example 5: Cleanup - Delete secret and scope
105+
// ===================================================================
106+
std::cout << "\n5. Cleaning up (deleting secret and scope):" << std::endl;
107+
std::cout << "-------------------------------------------" << std::endl;
108+
109+
std::cout << "Deleting secret: " << secret_key << std::endl;
110+
secrets.delete_secret(example_scope, secret_key);
111+
std::cout << "Secret deleted successfully!" << std::endl;
112+
113+
std::cout << "Deleting scope: " << example_scope << std::endl;
114+
secrets.delete_scope(example_scope);
115+
std::cout << "Scope deleted successfully!\n" << std::endl;
116+
117+
std::cout << "\n======================================" << std::endl;
118+
std::cout << "Secrets API example completed successfully!" << std::endl;
119+
} catch (const std::exception& e) {
120+
std::cerr << "Error: " << e.what() << std::endl;
121+
return 1;
122+
}
123+
124+
return 0;
125+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) 2025 Calvin Min
2+
// SPDX-License-Identifier: MIT
3+
#pragma once
4+
5+
#include "databricks/core/config.h"
6+
#include "databricks/secrets/secrets_types.h"
7+
8+
#include <optional>
9+
#include <vector>
10+
11+
namespace databricks {
12+
namespace internal {
13+
class IHttpClient;
14+
}
15+
16+
/**
17+
* @brief Client for interacting with the Databricks Secrets API
18+
*
19+
* The Secrets API allows you to securely store and manage credentials, tokens, and other
20+
* sensitive information in your Databricks workspace. Rather than entering credentials
21+
* directly into notebooks, you can store them as secrets and reference them securely.
22+
* This implementation uses Secrets API 2.0.
23+
*
24+
* Example usage:
25+
* @code
26+
* databricks::AuthConfig auth = databricks::AuthConfig::from_environment();
27+
* databricks::Secrets secrets(auth);
28+
*
29+
* // List all secret scopes
30+
* auto scopes = secrets.list_scopes();
31+
*
32+
* // Create a new secret scope
33+
* secrets.create_scope("my_scope", databricks::SecretScopeBackendType::DATABRICKS);
34+
*
35+
* // Store a secret
36+
* secrets.put_secret("my_scope", "api_key", "my_secret_value");
37+
*
38+
* // List secrets in a scope
39+
* auto secret_list = secrets.list_secrets("my_scope");
40+
*
41+
* // Delete a secret
42+
* secrets.delete_secret("my_scope", "api_key");
43+
* @endcode
44+
*/
45+
class Secrets {
46+
public:
47+
/**
48+
* @brief Construct a Secrets API client
49+
* @param auth Authentication configuration with host and token
50+
* @param api_version Secrets API version to use (default: "2.0")
51+
*/
52+
explicit Secrets(const AuthConfig& auth, const std::string& api_version = "2.0");
53+
54+
/**
55+
* @brief Construct a Secrets API client with dependency injection (for testing)
56+
* @param http_client Injected HTTP client (use MockHttpClient for unit tests)
57+
* @note This constructor is primarily for testing with mock HTTP clients
58+
*/
59+
explicit Secrets(std::shared_ptr<internal::IHttpClient> http_client);
60+
61+
/**
62+
* @brief Destructor
63+
*/
64+
~Secrets();
65+
66+
Secrets(const Secrets&) = delete;
67+
Secrets& operator=(const Secrets&) = delete;
68+
69+
// Scope operations
70+
/**
71+
* @brief List all secret scopes in the workspace
72+
*
73+
* @return Vector of SecretScope objects
74+
* @throws std::runtime_error if the API request fails
75+
*/
76+
std::vector<SecretScope> list_scopes();
77+
78+
/**
79+
* @brief Create a new secret scope
80+
*
81+
* @param scope The name of the secret scope to create
82+
* @param initial_manage_principal The principal (user or group) that will be granted MANAGE
83+
* permission on the new scope. Common values:
84+
* - "users": grants MANAGE access to all workspace users (default)
85+
* - "admins": grants MANAGE access to workspace admins only
86+
* - A specific user or group name
87+
* @param backend_type The type of backend (DATABRICKS or AZURE_KEYVAULT)
88+
* @param azure_resource_id Azure Key Vault resource ID (required for AZURE_KEYVAULT backend)
89+
* @param azure_tenant_id Azure tenant ID (required for AZURE_KEYVAULT backend)
90+
* @param dns_name Azure Key Vault DNS name (required for AZURE_KEYVAULT backend)
91+
* @throws std::runtime_error if the scope already exists or the API request fails
92+
* @throws std::invalid_argument if Azure parameters are missing for AZURE_KEYVAULT backend
93+
*
94+
* @note Databricks-backed scopes are stored in the control plane. Azure Key Vault-backed
95+
* scopes are stored in your Azure Key Vault instance.
96+
*/
97+
void create_scope(const std::string& scope, const std::string& initial_manage_principal,
98+
SecretScopeBackendType backend_type,
99+
const std::optional<std::string>& azure_resource_id = std::nullopt,
100+
const std::optional<std::string>& azure_tenant_id = std::nullopt,
101+
const std::optional<std::string>& dns_name = std::nullopt);
102+
103+
/**
104+
* @brief Delete a secret scope
105+
*
106+
* @param scope The name of the secret scope to delete
107+
* @throws std::runtime_error if the scope is not found or the API request fails
108+
*
109+
* @note Deleting a scope also deletes all secrets stored in that scope.
110+
* This operation cannot be undone.
111+
*/
112+
void delete_scope(const std::string& scope);
113+
114+
// Secret operations
115+
/**
116+
* @brief Store a secret as a string value
117+
*
118+
* @param scope The name of the secret scope
119+
* @param key The name of the secret to store
120+
* @param value The secret value as a string
121+
* @throws std::runtime_error if the API request fails
122+
*
123+
* @note The value parameter is passed by const reference to avoid unnecessary copies.
124+
* For enhanced security, users should securely clear the value from memory
125+
* after calling this function.
126+
*/
127+
void put_secret(const std::string& scope, const std::string& key, const std::string& value);
128+
129+
/**
130+
* @brief Delete a secret from a scope
131+
*
132+
* @param scope The name of the secret scope
133+
* @param key The name of the secret to delete
134+
* @throws std::runtime_error if the secret is not found or the API request fails
135+
*/
136+
void delete_secret(const std::string& scope, const std::string& key);
137+
138+
/**
139+
* @brief List all secrets in a scope
140+
*
141+
* @param scope The name of the secret scope
142+
* @return Vector of Secret objects containing metadata (not the secret values)
143+
* @throws std::runtime_error if the API request fails
144+
*
145+
* @note This method returns secret metadata only. Secret values cannot be retrieved
146+
* via the API for security reasons.
147+
*/
148+
std::vector<Secret> list_secrets(const std::string& scope);
149+
150+
private:
151+
class Impl;
152+
std::unique_ptr<Impl> pimpl_;
153+
154+
std::string backend_type_to_string(SecretScopeBackendType backend_type) const;
155+
156+
// Helper methods for parsing API responses
157+
static std::vector<SecretScope> parse_scopes_list(const std::string& json_str);
158+
static std::vector<Secret> parse_secrets_list(const std::string& json_str);
159+
};
160+
161+
} // namespace databricks

0 commit comments

Comments
 (0)