Skip to content

Commit

Permalink
core & ios: implement typed configurations & template (#328)
Browse files Browse the repository at this point in the history
Introduces a `Configuration` type which consumers will use to start instances of `Envoy`. This change allows us to:

- Provide an ergonomic interface for using Envoy Mobile without having to learn Envoy's configuration YAML structure
- Utilize a solid set of default settings for this library
- Prevent consumers from accessing features that are unsupported
- Validate configuration at compile time rather than runtime

This PR:
- Adds a templated configuration in a `.cc` file, exposed through the common layer all the way through to the platform layers
- Adds a `Configuration` type for accessing this configuration
- Updates the iOS example apps to use the new typed configuration instead of their own yaml files
- Maintains an existing initializer for exposing a string configuration for consumers who may still want this functionality

Tests will be added in a separate PR that updates this interface further with a builder for `Envoy` instances.

Resolves envoyproxy/envoy-mobile#169.

Related issue (why this is in C++ instead of a `yaml` resource): envoyproxy/envoy-mobile#341

Signed-off-by: Michael Rebello <[email protected]>
Signed-off-by: JP Simard <[email protected]>
  • Loading branch information
rebello95 authored and jpsim committed Nov 28, 2022
1 parent 391d2f7 commit a4bb8c4
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 36 deletions.
7 changes: 1 addition & 6 deletions mobile/examples/objective-c/hello_world/AppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ - (BOOL)application:(UIApplication *)application
[self.window setRootViewController:controller];
[self.window makeKeyAndVisible];

NSString *configFile = [[NSBundle mainBundle] pathForResource:@"config" ofType:@"yaml"];
NSString *configYaml = [NSString stringWithContentsOfFile:configFile
encoding:NSUTF8StringEncoding
error:NULL];
NSLog(@"Loading config:\n%@", configYaml);
self.envoy = [[Envoy alloc] initWithConfig:configYaml];
self.envoy = [[Envoy alloc] initWithConfig:[Configuration new]];
NSLog(@"Finished launching!");
return YES;
}
Expand Down
1 change: 0 additions & 1 deletion mobile/examples/objective-c/hello_world/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ objc_library(
"*.h",
"*.mm",
]),
data = ["config.yaml"],
deps = ["//dist:envoy_mobile_ios"],
)

Expand Down
1 change: 0 additions & 1 deletion mobile/examples/objective-c/hello_world/config.yaml

This file was deleted.

20 changes: 2 additions & 18 deletions mobile/examples/swift/hello_world/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import Envoy
import UIKit

private enum ConfigLoadError: Swift.Error {
case noFileAtPath
}

@UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {
private var envoy: Envoy!
private let envoy = try! Envoy()

var window: UIWindow?

func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
NSLog("Loading config")
let envoyConfig = try! self.loadEnvoyConfig()
NSLog("Loaded config:\n\(envoyConfig)")
self.envoy = Envoy(config: envoyConfig)

let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = ViewController()
window.makeKeyAndVisible()
Expand All @@ -27,12 +19,4 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {

return true
}

private func loadEnvoyConfig() throws -> String {
guard let configFile = Bundle.main.path(forResource: "config", ofType: "yaml") else {
throw ConfigLoadError.noFileAtPath
}

return try String(contentsOfFile: configFile, encoding: .utf8)
}
}
1 change: 0 additions & 1 deletion mobile/examples/swift/hello_world/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "appmain",
srcs = glob(["*.swift"]),
data = ["config.yaml"],
deps = ["//dist:envoy_mobile_ios"],
)

Expand Down
1 change: 0 additions & 1 deletion mobile/examples/swift/hello_world/config.yaml

This file was deleted.

7 changes: 5 additions & 2 deletions mobile/library/common/BUILD
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
licenses(["notice"]) # Apache 2

load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_binary", "envoy_cc_library", "envoy_package")
load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package")

envoy_package()

envoy_cc_library(
name = "envoy_main_interface_lib",
srcs = ["main_interface.cc"],
srcs = [
"config_template.cc",
"main_interface.cc",
],
hdrs = ["main_interface.h"],
repository = "@envoy",
deps = [
Expand Down
90 changes: 90 additions & 0 deletions mobile/library/common/config_template.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// NOLINT(namespace-envoy)

const char* config_template = R"(
static_resources:
listeners:
- address:
socket_address: {address: 0.0.0.0, port_value: 9000, protocol: TCP}
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stat_prefix: base
route_config:
virtual_hosts:
- name: all
domains: ["*"]
routes:
- match: {prefix: "/"}
route: {cluster: base}
http_filters:
- name: envoy.router
- address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9001
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
stat_prefix: client
http_protocol_options:
allow_absolute_url: true
route_config:
name: local_route
virtual_hosts:
- name: all
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: default_egress
http_filters:
- name: envoy.filters.http.dynamic_forward_proxy
config:
dns_cache_config:
name: dynamic_forward_proxy_cache_config
dns_lookup_family: AUTO
- name: envoy.router
clusters:
- name: base
connect_timeout: {{ connect_timeout }}
dns_refresh_rate: {{ dns_refresh_rate }}
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: base
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: {address: example.com, port_value: 443}
tls_context:
sni: example.com
type: LOGICAL_DNS
- name: default_egress
connect_timeout: {{ connect_timeout }}
dns_refresh_rate: {{ dns_refresh_rate }}
lb_policy: CLUSTER_PROVIDED
cluster_type:
name: envoy.clusters.dynamic_forward_proxy
typed_config:
"@type": type.googleapis.com/envoy.config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig
dns_cache_config:
name: dynamic_forward_proxy_cache_config
dns_lookup_family: AUTO
# WARNING!
# TODO: Enable TLS in https://github.com/lyft/envoy-mobile/issues/322
#
# tls_context:
# common_tls_context:
# validation_context:
# trusted_ca: {filename: /etc/ssl/certs/ca-certificates.crt}
stats_flush_interval: {{ stats_flush_interval }}
watchdog:
megamiss_timeout: 60s
miss_timeout: 60s
)";
8 changes: 6 additions & 2 deletions mobile/library/common/include/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ envoy_package()

envoy_cc_library(
name = "c_types_interface",
srcs = ["c_types.cc"],
hdrs = ["c_types.h"],
srcs = [
"c_types.cc",
],
hdrs = [
"c_types.h",
],
repository = "@envoy",
)
7 changes: 7 additions & 0 deletions mobile/library/common/main_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
extern "C" { // functions
#endif

/**
* Template configuration compiled with the Envoy Mobile library.
* More information about Envoy's config can be found at:
* https://www.envoyproxy.io/docs/envoy/latest/configuration/configuration
*/
extern const char* config_template;

/**
* Initialize an underlying HTTP stream.
* @param engine, handle to the engine that will manage this stream.
Expand Down
14 changes: 14 additions & 0 deletions mobile/library/objective-c/EnvoyEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,20 @@ typedef NSDictionary<NSString *, NSArray<NSString *> *> EnvoyHeaders;

@end

#pragma mark - EnvoyConfiguration

/// Namespace for Envoy configurations.
@interface EnvoyConfiguration : NSObject

/**
Provides a default configuration template that may be used for starting Envoy.
@return A template that may be used as a starting point for constructing configurations.
*/
+ (NSString *)templateString;

@end

#pragma mark - EnvoyEngineImpl

// Concrete implementation of the `EnvoyEngine` protocol.
Expand Down
10 changes: 10 additions & 0 deletions mobile/library/objective-c/EnvoyEngine.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ - (EnvoyHTTPStream *)startStreamWithObserver:(EnvoyObserver *)observer {

@end

#pragma mark - EnvoyConfiguration

@implementation EnvoyConfiguration

+ (NSString *)templateString {
return [[NSString alloc] initWithUTF8String:config_template];
}

@end

#pragma mark - Utilities to move elsewhere

typedef struct {
Expand Down
1 change: 1 addition & 0 deletions mobile/library/swift/src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ swift_static_framework(
name = "ios_framework",
srcs = [
"Client.swift",
"Configuration.swift",
"Envoy.swift",
"Error.swift",
"LogLevel.swift",
Expand Down
62 changes: 62 additions & 0 deletions mobile/library/swift/src/Configuration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Foundation

/// Error that may be thrown by the configuration type.
@objc
public enum ConfigurationError: Int, Swift.Error {
/// Not all keys within the provided template were resolved.
case unresolvedTemplateKey
}

/// Builder used for creating configurations for new Envoy instances.
@objcMembers
public final class Configuration: NSObject {
/// Timeout to apply to connections.
public private(set) var connectTimeoutSeconds: UInt = 30

/// Interval at which DNS should be refreshed.
public private(set) var dnsRefreshSeconds: UInt = 60

/// Interval at which stats should be flushed.
public private(set) var statsFlushSeconds: UInt = 60

public func withConnectTimeoutSeconds(_ connectTimeoutSeconds: UInt) -> Configuration {
self.connectTimeoutSeconds = connectTimeoutSeconds
return self
}

public func withDNSRefreshSeconds(_ dnsRefreshSeconds: UInt) -> Configuration {
self.dnsRefreshSeconds = dnsRefreshSeconds
return self
}

public func withStatsFlushSeconds(_ statsFlushSeconds: UInt) -> Configuration {
self.statsFlushSeconds = statsFlushSeconds
return self
}

// MARK: - Internal

/// Converts the structure into a file by replacing values in a default template configuration.
///
/// - returns: A file that may be used for starting an instance of Envoy.
public func build() throws -> String {
var template = EnvoyConfiguration.templateString()
let templateKeysToValues: [String: String] = [
"connect_timeout": "\(self.connectTimeoutSeconds)s",
"dns_refresh_rate": "\(self.dnsRefreshSeconds)s",
"stats_flush_interval": "\(self.statsFlushSeconds)s",
]

for (templateKey, value) in templateKeysToValues {
while let range = template.range(of: "{{ \(templateKey) }}") {
template = template.replacingCharacters(in: range, with: value)
}
}

if template.contains("{{") {
throw ConfigurationError.unresolvedTemplateKey
}

return template
}
}
16 changes: 12 additions & 4 deletions mobile/library/swift/src/Envoy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@ public final class Envoy: NSObject {
return self.runner.isFinished
}

/// Initialize a new Envoy instance.
/// Initialize a new Envoy instance using a typed configuration.
///
/// - parameter config: Configuration file that is recognizable by Envoy (YAML).
/// - parameter config: Configuration to use for starting Envoy.
/// - parameter logLevel: Log level to use for this instance.
public init(config: String, logLevel: LogLevel = .info) {
self.runner = RunnerThread(config: config, engine: self.engine, logLevel: logLevel)
public convenience init(config: Configuration = Configuration(), logLevel: LogLevel = .info) throws {
self.init(configYAML: try config.build(), logLevel: logLevel)
}

/// Initialize a new Envoy instance using a string configuration.
///
/// - parameter configYAML: Configuration YAML to use for starting Envoy.
/// - parameter logLevel: Log level to use for this instance.
public init(configYAML: String, logLevel: LogLevel = .info) {
self.runner = RunnerThread(config: configYAML, engine: self.engine, logLevel: logLevel)
self.runner.start()
}

Expand Down

0 comments on commit a4bb8c4

Please sign in to comment.