Skip to content

Conversation

THardy98
Copy link
Contributor

What was changed

Added environment configuration

  1. Closes [Feature Request] Environment Configuration #287

  2. How was this tested:
    Added test suite

  3. Any docs updates needed?
    Yes

@THardy98 THardy98 changed the title envconfig impl Environment configuration Aug 25, 2025
@THardy98 THardy98 marked this pull request as ready for review August 25, 2025 21:33
@THardy98 THardy98 requested a review from a team as a code owner August 25, 2025 21:33
# TLS configuration as specified as part of client configuration
#
# @!attribute [r] disabled
# @return [Boolean] If true, TLS is explicitly disabled
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this Boolean, nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Folding this into the comment below

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unresolving and mentioned later, remember this is a tri-state bool if we want this to properly represent what's in TOML

Copy link
Contributor Author

@THardy98 THardy98 Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oop sorry - I knew I had this remaining, was updating the submodule, didn't realize i resolved this

updated

# Create a ClientConfigTLS from a hash
# @param hash [Hash, nil] Hash representation
# @return [ClientConfigTLS, nil] The TLS configuration or nil if hash is nil/empty
def self.from_hash(hash)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrmm, help me remember, why do we have this (and the to_hash) functionality exposed to users? We don't in Go but I saw we do in Python. If it's for TOML serialization ability, I figure that's at the profile level, but even if we do want it here, shouldn't that mean disabled can be a boolean or nil? There's a difference between setting disabled as false and not having it set at all I think.

If we are going to have a to and from hash, they should be symmetrical and exactly represent what is in TOML without defaults

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

temporalio/sdk-python#895 (comment)

That's a good point. I've mimicked Python here which exposes disabled only as a bool, this is because it's only exposed as a bool from core:

struct TomlClientConfigTLS {
    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    disabled: bool,

    #[serde(skip_serializing_if = "Option::is_none")]
    client_cert_path: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    client_cert_data: Option<String>, // String in TOML, not Vec<u8>

    #[serde(skip_serializing_if = "Option::is_none")]
    client_key_path: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    client_key_data: Option<String>, // String in TOML, not Vec<u8>

    #[serde(skip_serializing_if = "Option::is_none")]
    server_ca_cert_path: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    server_ca_cert_data: Option<String>, // String in TOML, not Vec<u8>

    #[serde(skip_serializing_if = "Option::is_none")]
    server_name: Option<String>,

    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    disable_host_verification: bool,
}

impl TomlClientConfigTLS {
    fn new() -> Self {
        Self {
            disabled: false,
            client_cert_path: None,
            client_cert_data: None,
            client_key_path: None,
            client_key_data: None,
            server_ca_cert_path: None,
            server_ca_cert_data: None,
            server_name: None,
            disable_host_verification: false,
        }
    }

which uses that default and populates it with the given config. disabled is the only field that is non optional.

Copy link
Contributor Author

@THardy98 THardy98 Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we think this is blocking, I can open a PR. Might be best to do this now given that only Python is released so far.

Copy link
Member

@cretz cretz Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if we want users to be able to convert this to and from TOML, it should represent exactly what the TOML is I figured, right? It's only when we convert to the options a client uses for connect that we should apply defaults IMO.

Up to you on the scheduling of the fix, but yeah prior to marking GA. But if done in separate issue, we might as well remove the to_h/from_h from this PR if we want to get this one merged before that. Up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

temporalio/sdk-core#1002
opened core PR

Comment on lines 143 to 145
# If it's a string path (from TOML), read the file
# Otherwise return as content
if File.exist?(source)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should know whether this is a path or not from TOML, should not have to use File.exists to switch between behaviors

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TOML fields passed in as file paths are now read as Pathname, only data fields are string

@@ -1,5 +1,6 @@
# frozen_string_literal: true

require 'temporalio/envconfig'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't matter much, but I think this should only be required where it is used, though I could also see it being required from client.rb as a convenience, but I don't think it needs to be required from top-level for convenience. I should have said the same about versioning_override when it was added here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump here, we should remove this IMO

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missed this, removed

let hash = RHash::new();
match ds {
DataSource::Path(p) => {
hash.aset("path", ruby.str_new(p))?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the hashes in this Rust library should use symbols for keys IMO. But now that I think about it, maybe we should be returning a Rust struct that is accessible in Ruby. Maybe something like https://docs.rs/magnus/latest/magnus/derive.TypedData.html. Or if we did want struct/data, https://docs.rs/magnus/latest/magnus/struct.Ruby.html#method.define_struct or https://docs.rs/magnus/latest/magnus/struct.Ruby.html#method.define_data.

Not sure, can discuss, but I think hashes of string keys is probably the least safe/efficient for complex objects like these. While efficiency doesn't strictly matter for non-user-facing code and for how rare this is called, I think we should set a reasonable example for complex objects returned to Ruby from calls in Rust.

EDIT: Revisiting the Core envconfig layer, I wonder if it'd be worth having the Rust side serialize to a TOML-like-but-still-typesafe structure, and then we can convert that to Ruby hash. It probably would not be hard to have a serde-to-Ruby-hash. Sure it would still be string hash keys, but it would be cleaner I think. There's not that many data types to convert from TOML/Rust to Ruby.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opted to just use symbols. I'd like to avoid more extensive changes here to get this in.

)
}

// load_client_connect_config(profile: String|nil, path: String|nil, data: String|nil, disable_file: bool, disable_env: bool, config_file_strict: bool, env_vars: Hash|nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to just accept a defined-in-Ruby struct options set as we do in other Rust-from-Ruby calls if that makes it cleaner once it gets to this many parameters

- Refactor ClientConfigTLS, ClientConfigProfile, and ClientConfig to use Data.define
- Rename from_hash → from_h for all envconfig classes
- Rename to_hash → to_h for all envconfig classes
- Rename `to_connect_tls_config` → `to_tls_options`
- Return `Connection::TLSOptions` object instead of plain Hash
Changed `to_client_connect_options` to return `[positional_args, keyword_args]`
tuple instead of a hash, enabling clean one-liner client connections:

```ruby
args, kwargs = profile.to_client_connect_options
client = Temporalio::Client.connect(*args, **kwargs)
```
…ction

- TOML paths (*_path fields) become Pathname objects
- TOML data (*_data fields) remain String objects
Changed all hash keys from strings to symbols in the Rust-to-Ruby bridge
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized, I think this file should be called env_config.rb to match convention if we're capitalizing the C in EnvConfig (which is proper IMO)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed the file

# - String: TOML configuration content
# - nil: No configuration source

# Convert a data source to path and data parameters for the bridge
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem to be prefixed with underscore

@@ -1,5 +1,6 @@
# frozen_string_literal: true

require 'temporalio/envconfig'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump here, we should remove this IMO

when Pathname
[source.to_s, nil]
when String
[nil, source.encode('UTF-8').bytes]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[nil, source.encode('UTF-8').bytes]
[nil, source.b]

Shouldn't need to build an array of bytes to pass to Rust, a Ruby string is just fine. Can just accept a https://docs.rs/magnus/latest/magnus/r_string/struct.RString.html on the Rust side instead of a String, and on the Rust side, call unsafe { whatever_your_var_is.as_slice().to_vec() } to get a Vec<u8>.

# TLS configuration as specified as part of client configuration
#
# @!attribute [r] disabled
# @return [Boolean] If true, TLS is explicitly disabled
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unresolving and mentioned later, remember this is a tri-state bool if we want this to properly represent what's in TOML

@THardy98 THardy98 requested a review from cretz September 9, 2025 18:11
Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, only blocking concern is the other stuff showing up in the PR changeset, but updating branch with main may fix that, then I'll mark approved,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why all of this other stuff is showing up in the PR, may just need to merge from main

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup - mostly resolved via merge from main. Had to revert the changes in cancellation.rb explicitly

@THardy98 THardy98 requested a review from cretz September 9, 2025 22:30
Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, nothing blocking, thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in theory all of this is "experimental" and we usually should mark it as such (same for Python IMO)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

marked experimental

@THardy98 THardy98 merged commit 51b5f95 into main Sep 10, 2025
8 checks passed
@THardy98 THardy98 deleted the env_config branch September 10, 2025 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature Request] Environment Configuration
2 participants