зеркало из https://github.com/dotnet/tye.git
Implement library support for service discovery (#299)
Implements two flavor of library support for service-discovery: - GetConnectionString (arbitrary strings) augmenting existing functionality already in asp.net core - GetServiceUri (uris) can be combined from a protocol/host/port triple See the **extensive** doc `service_discovery.md` that is filled out in this PR. That documents pretty much everything about how this works now.
This commit is contained in:
Родитель
7729500176
Коммит
083a97f353
|
@ -63,22 +63,26 @@ This tutorial will demonstrate how to use [`tye run`](commandline/tye-run.md) to
|
|||
|
||||
## Getting the frontend to communicate with the backend
|
||||
|
||||
Now that we have two applications running, let's make them communicate. By default, `tye` enables service discovery by injecting environment variables with a specific naming convention.
|
||||
Now that we have two applications running, let's make them communicate. By default, `tye` enables service discovery by injecting environment variables with a specific naming convention. For more information on, see [service discovery](service_discovery.md).
|
||||
|
||||
1. Open the solution in your editor of choice.
|
||||
1. Add a NuGet.config to add the `dotnet-core` package source.
|
||||
|
||||
1. Add a `GetUri()` method to the frontend project at the bottom of the Startup.cs class:
|
||||
Paste the following into `nuget.config` in the `microservice/` directory.
|
||||
|
||||
```C#
|
||||
private Uri GetUri(IConfiguration configuration, string name)
|
||||
{
|
||||
return new Uri($"http://{configuration[$"service:{name}:host"]}:{configuration[$"service:{name}:port"]}");
|
||||
}
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
|
||||
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
This method resolved the URL using the `tye` naming convention for services. For more information on, see [service discovery](service_discovery.md).
|
||||
2. Open the solution in your editor of choice.
|
||||
|
||||
2. Add a file `WeatherForecast.cs` to the `frontend` project.
|
||||
3. Add a file `WeatherForecast.cs` to the `frontend` project.
|
||||
|
||||
```C#
|
||||
using System;
|
||||
|
@ -100,7 +104,7 @@ Now that we have two applications running, let's make them communicate. By defau
|
|||
|
||||
This will match the backend `WeatherForecast.cs`.
|
||||
|
||||
3. Add a file `WeatherClient.cs` to the `frontend` project with the following contents:
|
||||
4. Add a file `WeatherClient.cs` to the `frontend` project with the following contents:
|
||||
|
||||
```C#
|
||||
using System.Net.Http;
|
||||
|
@ -134,7 +138,13 @@ Now that we have two applications running, let's make them communicate. By defau
|
|||
}
|
||||
```
|
||||
|
||||
4. Now register this client in `Startup.cs` class in `ConfigureServices` of the `frontend` project:
|
||||
5. Add a reference to the `Microsoft.Tye.Extensions.Configuration` package to the frontend project
|
||||
|
||||
```txt
|
||||
dotnet add frontend/frontend.csproj package Microsoft.Tye.Extensions.Configuration --version "0.1.0-*"
|
||||
```
|
||||
|
||||
6. Now register this client in `Startup.cs` class in `ConfigureServices` of the `frontend` project:
|
||||
|
||||
```C#
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
|
@ -143,14 +153,14 @@ Now that we have two applications running, let's make them communicate. By defau
|
|||
|
||||
services.AddHttpClient<WeatherClient>(client =>
|
||||
{
|
||||
client.BaseAddress = GetUri(Configuration, "backend");
|
||||
client.BaseAddress = Configuration.GetServiceUri("backend");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
This will wire up the `WeatherClient` to use the correct URL for the `backend` service.
|
||||
|
||||
5. Add a `Forecasts` property to the `Index` page model under `Pages\Index.cshtml.cs` in the `frontend` project.
|
||||
7. Add a `Forecasts` property to the `Index` page model under `Pages\Index.cshtml.cs` in the `frontend` project.
|
||||
|
||||
```C#
|
||||
public WeatherForecast[] Forecasts { get; set; }
|
||||
|
@ -165,7 +175,7 @@ Now that we have two applications running, let's make them communicate. By defau
|
|||
}
|
||||
```
|
||||
|
||||
6. Change the `Index.cshtml` razor view to render the `Forecasts` property in the razor page:
|
||||
8. Change the `Index.cshtml` razor view to render the `Forecasts` property in the razor page:
|
||||
|
||||
```cshtml
|
||||
@page
|
||||
|
@ -204,7 +214,7 @@ Now that we have two applications running, let's make them communicate. By defau
|
|||
</table>
|
||||
```
|
||||
|
||||
7. Run the project with [`tye run`](commandline/tye-run.md) and the `frontend` service should be able to successfully call the `backend` service!
|
||||
9. Run the project with [`tye run`](commandline/tye-run.md) and the `frontend` service should be able to successfully call the `backend` service!
|
||||
|
||||
When you visit the `frontend` service you should see a table of weather data. This data was produced randomly in the `backend` service. The fact that you're seeing it in a web UI in the `frontend` means that the services are able to communicate.
|
||||
|
||||
|
|
|
@ -44,3 +44,15 @@ If you already have a build installed and you want to update, replace `install`
|
|||
dotnet tool update -g Microsoft.Tye --version "0.1.0-*" --add-source https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json
|
||||
```
|
||||
|
||||
If you are using CI builds of Tye we also recommend using CI builds of our libraries as well with the matching version. To add the `dotnet-core` package source add a `NuGet.config` to your repository or solution directory.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
|
||||
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
```
|
||||
|
|
|
@ -54,6 +54,7 @@ We just showed how `tye` makes it easier to communicate between 2 applications r
|
|||
```
|
||||
cd backend/
|
||||
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
|
||||
cd ..
|
||||
```
|
||||
|
||||
3. Modify `Startup.ConfigureServices` in the `backend` project to add the redis `IDistributedCache` implementation.
|
||||
|
@ -64,19 +65,11 @@ We just showed how `tye` makes it easier to communicate between 2 applications r
|
|||
|
||||
services.AddStackExchangeRedisCache(o =>
|
||||
{
|
||||
var connectionString = Configuration["connectionstring:redis"];
|
||||
if (connectionString != null)
|
||||
{
|
||||
o.Configuration = connectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
o.Configuration = $"{Configuration["service:redis:host"]}:{Configuration["service:redis:port"]}";
|
||||
}
|
||||
o.Configuration = Configuration.GetConnectionString("redis");
|
||||
});
|
||||
}
|
||||
```
|
||||
The above configures redis to use the host and port for the `redis` service injected by the `tye` host.
|
||||
The above configures redis to the configuration string for the `redis` service injected by the `tye` host.
|
||||
|
||||
4. Modify `tye.yaml` to include redis as a dependency.
|
||||
|
||||
|
@ -93,6 +86,7 @@ We just showed how `tye` makes it easier to communicate between 2 applications r
|
|||
image: redis
|
||||
bindings:
|
||||
- port: 6379
|
||||
connectionString: "${host}:${port}"
|
||||
- name: redis-cli
|
||||
image: redis
|
||||
args: "redis-cli -h redis MONITOR"
|
||||
|
@ -100,6 +94,8 @@ We just showed how `tye` makes it easier to communicate between 2 applications r
|
|||
|
||||
We've added 2 services to the `tye.yaml` file. The `redis` service itself and a `redis-cli` service that we will use to watch the data being sent to and retrieved from redis.
|
||||
|
||||
> :bulb: The `"${host}:${port}"` format in the `connectionString` property will substitute the values of the host and port number to produce a connection string that can be used with StackExchange.Redis.
|
||||
|
||||
5. Run the `tye` command line in the solution root
|
||||
|
||||
> :bulb: Make sure your command-line is in the `microservices/` directory. One of the previous steps had you change directories to edit a specific project.
|
||||
|
@ -138,11 +134,11 @@ We just showed how `tye` makes it easier to communicate between 2 applications r
|
|||
|
||||
```text
|
||||
cd backend
|
||||
dotnet add package Microsoft.Tye.Extensions.Configuration
|
||||
dotnet add package Microsoft.Tye.Extensions.Configuration --version "0.1.0-*"
|
||||
cd ..
|
||||
```
|
||||
|
||||
Then update `CreateHostBuilder` to create the configuration source:
|
||||
Then update `CreateHostBuilder` in `Program.cs` of the `backend` project to create the configuration source:
|
||||
|
||||
```C#
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
|
@ -183,7 +179,7 @@ We just showed how `tye` makes it easier to communicate between 2 applications r
|
|||
```text
|
||||
Validating Secrets...
|
||||
Enter the connection string to use for service 'redis': redis:6379
|
||||
Created secret 'binding-production-redis-redis-secret'.
|
||||
Created secret 'binding-production-redis-secret'.
|
||||
```
|
||||
|
||||
> :question: `--interactive` is needed here to create the secret. This is a one-time configuration step. In a CI/CD scenario you would not want to have to specify connection strings over and over, deployment would rely on the existing configuration in the cluster.
|
||||
|
|
|
@ -1 +1,307 @@
|
|||
TODO
|
||||
# Service Discovery
|
||||
|
||||
Service discovery is a general term that describes the process by which one service figures out the address of another service. Put another way...
|
||||
|
||||
> If I need to talk to the backend... how do I figure out the right URI? In production? In my staging environment? In my local development?
|
||||
|
||||
There are lots of possible different ways that service discovery could be done, with varying levels of complexity.
|
||||
|
||||
## Tye's philosophy for service discovery
|
||||
|
||||
Tye aims to provide a solution that:
|
||||
|
||||
- Works the same way in local development and cloud environments
|
||||
- Is based on simple primitives
|
||||
- Avoids the need for external infrastructure
|
||||
|
||||
Using Tye's service discovery features is *optional*. If you already have a scheme you like, or if you are using a programming model that solves service discovery in a different way then you can feel free to ignore it :+1:.
|
||||
|
||||
---
|
||||
|
||||
Tye uses a combination of environment variables and files on disk for specifying connection strings and URIs of services.
|
||||
|
||||
- Enviroment variables are used where possible because they are simple
|
||||
- Files on disk are used for secrets in deployed applications because they are more secure
|
||||
- Both of these are primitives that can be accessed from any programming langauge
|
||||
|
||||
Our philosophy is that automating something you could do yourself is better than doing *magic* or requiring external services.
|
||||
|
||||
It is our recommendation you avoid any hardcoding of URIs/addresses of other services in application code. Use service discovery via configuration so that deploying to different environments is easy.
|
||||
|
||||
## How to do service discovery in .NET Applications
|
||||
|
||||
The simple way to use Tye's service discovery is through the `Microsoft.Extensions.Configuration` system - available by default in ASP.NET Core or .NET Core Worker projects. In addition to this we provide the `Microsoft.Tye.Extensions.Configuration` package with some Tye-specific extensions layered on top of the configuration system.
|
||||
|
||||
---
|
||||
|
||||
To access URIs use the `GetServiceUri()` extension method and provide the service name.
|
||||
|
||||
```C#
|
||||
// Get the URI of the 'backend' service and create an HttpClient.
|
||||
var uri = Configuration.GetServiceUri("backend");
|
||||
var httpClient = new HttpClient()
|
||||
{
|
||||
BaseAddress = uri
|
||||
};
|
||||
```
|
||||
|
||||
If the service provides multiple bindings, provide the binding name as well.
|
||||
|
||||
```C#
|
||||
// Get the URI of the 'backend' service and create an HttpClient.
|
||||
var uri = Configuration.GetServiceUri(service: "backend", binding: "myBinding");
|
||||
var httpClient = new HttpClient()
|
||||
{
|
||||
BaseAddress = uri
|
||||
};
|
||||
```
|
||||
|
||||
URIs are available by default for all of your services and bindings. A URI will not be available through the service discovery system for bindings that provide a `connectionString` in config.
|
||||
|
||||
---
|
||||
|
||||
To access a connection string, use the `GetConnectionString()` method and provide the service name.
|
||||
|
||||
```C#
|
||||
// Get the connection string of the 'postgres' service and open a database connection.
|
||||
var connectionString = Configuration.GetConnectionString("postgres");
|
||||
using (var connection = new NpgsqlConnection(connectionString))
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
To get the connection string for a named binding, pass the binding name as well.
|
||||
|
||||
```C#
|
||||
// Get the connection string of the 'postgres' service and open a database connection.
|
||||
var connectionString = Configuration.GetConnectionString(service: "postgres", binding: "myBinding");
|
||||
using (var connection = new NpgsqlConnection(connectionString))
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Connection strings will be available for bindings that use the `connectionString` property in configuration.
|
||||
|
||||
---
|
||||
|
||||
Specifying a connection string in `tye.yaml` will usually involve the use of templating to fill in values that are provided by Tye.
|
||||
|
||||
Example: Redis
|
||||
|
||||
```yaml
|
||||
services:
|
||||
- name: redis
|
||||
image: redis
|
||||
bindings:
|
||||
- port: 6379
|
||||
connectionString: ${host}:${port}
|
||||
```
|
||||
|
||||
This fragment will launch `redis` when used with `tye run` on port `6379` (the typical listening port for Redis) **and** will provide a connection string to other services with the value of `localhost:6379`.
|
||||
|
||||
> :bulb: It's preferrable to use `${host}` over hardcoding the string `localhost` - for instance `localhost` will not work inside a container. You'll usually see `localhost` as the names of services, but Tye has features that will replace this with hostname values that work from containers.
|
||||
|
||||
---
|
||||
|
||||
Templating of connection strings can be used to avoid duplication.
|
||||
|
||||
Example: Postgres
|
||||
|
||||
```yaml
|
||||
services:
|
||||
- name: postgres
|
||||
image: postgres
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: "pass@word1"
|
||||
bindings:
|
||||
- port: 5432
|
||||
connectionString: Server=${host};Port=${port};User Id=postgres;Password=${env:POSTGRES_PASSWORD};
|
||||
```
|
||||
|
||||
In this case a `postgres` container is being passed its password via the `POSTGRES_PASSWORD` value. The token `${env:POSTGRES_PASSWORD}` will be replaced with the value from `POSTGRES_PASSWORD` to avoid repetition.
|
||||
|
||||
Currently replacement of environment variables using this mechanism is limited to environment variables defined in `tye.yaml`.
|
||||
|
||||
This is a typical pattern for initializing a database for local development - initializing the password and passing it to the application in the same place.
|
||||
|
||||
> :bulb: Avoid generating connection strings, or hardcoding connection string parameters in application code. Tye allows you to configure connection strings differently between local development and deployed apps. The `connectionString` property in `tye.yaml` is not used in deployed applications, it's only for development.
|
||||
|
||||
|
||||
## How it works: URIs in development
|
||||
|
||||
For URIs, the Tye infrastructure will generate a set of environment variables using a well-known pattern. These environment variables will through through the configuration system and by used by `GetServiceUri()`.
|
||||
|
||||
These are normal environment variables and can be read directly or through the configuration system.
|
||||
|
||||
The pattern for a default binding on the `backend` service:
|
||||
|
||||
| | Environment Variable | Configuration Key |
|
||||
|----------|------------------------------|----------------------------|
|
||||
| Protocol | `SERVICE__BACKEND__PROTOCOL` | `service:backend:protocol` |
|
||||
| Host | `SERVICE__BACKEND__HOST` | `service:backend:host` |
|
||||
| Port | `SERVICE__BACKEND__PORT` | `service:backend:port` |
|
||||
|
||||
|
||||
The pattern for a named binding called `myBinding` on the `backend` service:
|
||||
|
||||
| | Environment Variable | Configuration Key |
|
||||
|----------|-----------------------------------------|--------------------------------------|
|
||||
| Protocol | `SERVICE__BACKEND__MYBINDING__PROTOCOL` | `service:backend:mybinding:protocol` |
|
||||
| Host | `SERVICE__BACKEND__MYBINDING__HOST` | `service:backend:mybinding:host` |
|
||||
| Port | `SERVICE__BACKEND__MYBINDING__PORT` | `service:backend:mybinding:port` |
|
||||
|
||||
|
||||
> :bulb: That's a double-underscore (`__`) in the environment variables. The `Microsoft.Extensions.Configuration` system uses double-underscore as a separator because single underscores are already common in environment variables.
|
||||
|
||||
---
|
||||
|
||||
Here's a walkthrough of how this works in practice, using the following example application:
|
||||
|
||||
```yaml
|
||||
name: frontend-backend
|
||||
services:
|
||||
- name: backend
|
||||
project: backend/backend.csproj
|
||||
- name: frontend
|
||||
project: frontend/frontend.csproj
|
||||
```
|
||||
|
||||
These services are ASP.NET Core projects. The following set of steps takes place in development when doing `tye run`.
|
||||
|
||||
1. Since these are ASP.NET Core projects, Tye infers an `http` default binding, and an `https` named binding (called `https`) for each project. Since these bindings don't have any explicit configuration, ports will be automatically assigned by the host.
|
||||
|
||||
2. Tye will assign each binding an available port (avoiding conflicts).
|
||||
|
||||
3. Tye will generate a set of environment variables for each binding based on the assigned ports. The protocol of each binding was inferred (`http` or `https`) and the host will be `localhost` since this is local development.
|
||||
|
||||
4. Each service is given access to the environment variables that contain the bindings of the *other* services in the application. So `frontend` has access to the bindings of `backend` and vice-versa. These environment variables are passed when launching the process by the Tye host.
|
||||
|
||||
5. On startup, the environment variable configuration source reads all environment variables and translates them to the config key format (see table above).
|
||||
|
||||
6. When the application calls `GetServiceUri("backend")`, the method will read the `service:backend:protocol`, `service:backend:host`, `service:backend:port` variables and combine them into a URI.
|
||||
|
||||
## How it works: Connection Strings in development
|
||||
|
||||
When a `binding` element in `tye.yaml` sets the `connectionString` property, then Tye will not set the environment variables used for URIs (previous section). Tye will instead build a connection string and make that available.
|
||||
|
||||
Connection strings are typically used when a URI is not the right solution. For example, databases often have many configurable parameters beyond the hostname and port of the server.
|
||||
|
||||
Use the `GetConnectionString()` method to read connection strings.
|
||||
|
||||
These are normal environment variables and can be read directly or through the configuration system.
|
||||
|
||||
The pattern for a default binding on the `postgres` service:
|
||||
|
||||
| | Environment Variable | Configuration Key |
|
||||
|-------------------|------------------------------|----------------------------|
|
||||
| Connection String | `CONNECTIONSTRING__POSTGRES` | `connectionstrings:postgres` |
|
||||
|
||||
|
||||
The pattern for a named binding called `myBinding` on the `postgres` service:
|
||||
|
||||
| | Environment Variable | Configuration Key |
|
||||
|-------------------|------------------------------|----------------------------|
|
||||
| Connection String | `CONNECTIONSTRING__POSTGRES__MYBINDING` | `connectionstrings:postgres:mybinding` |
|
||||
|
||||
> :bulb: That's a double-underscore (`__`) in the environment variables. The `Microsoft.Extensions.Configuration` system uses double-underscore as a separator because single underscores are already common in environment variables.
|
||||
|
||||
Here's a walkthrough of how this works in practice, using the following example application:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
- name: backend
|
||||
project: backend/backend.csproj
|
||||
- name: frontend
|
||||
project: frontend/frontend.csproj
|
||||
- name: postgres
|
||||
image: postgres
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: "pass@word1"
|
||||
bindings:
|
||||
- port: 5432
|
||||
connectionString: Server=${host};Port=${port};User Id=postgres;Password=${env:POSTGRES_PASSWORD};
|
||||
```
|
||||
|
||||
We'll follow the example of the connection string for `postgres` is generated and made available to `frontend` and `backend`. The following set of steps takes place in development when doing `tye run`.
|
||||
|
||||
1. The `postgres` service has a single binding with a hardcoded port. If the port was unspecified then Tye will assign each binding an available port (avoiding conflicts).
|
||||
|
||||
2. Tye will substitute the values of `${host}` and `${port}` from the binding. Tye will substitue the value of `POSTGRES_PASSWORD` for `${env:POSTGRES_PASSWORD}`. The result is generate as the `CONNECTIONSTRINGS__POSTGRES` environment variable.
|
||||
|
||||
3. Each service is given access to the environment variables that contain the bindings of the *other* services in the application. So `frontend` and `backend` both have access to each-other's bindings as well as the environment variable `CONNECTIONSTRINGS__POSTGRES`. The Tye host will provide these environment variables when launching application processes.
|
||||
|
||||
4. On startup, the environment variable configuration source reads all environment variables and translates them to the config key format (see table above).
|
||||
|
||||
5. When the application calls `GetConnectionString("postgres")`, the method will read the `connectionstrings:postgres` key and return the value.
|
||||
|
||||
## How it works: Deployed applications
|
||||
|
||||
When deploying an application, Tye will deploy all of the containers built from your .NET projects. However, Tye is not able to deploy your application's dependencies - since Tye is orchestrating things, it needs to know what values (URIs and connection strings) to provide to application code.
|
||||
|
||||
---
|
||||
|
||||
When deploying your .NET projects, Tye will use the environment variable format described above to to set environment variables on your pods and containers.
|
||||
|
||||
To avoid hardcoding ephemeral details like pod names, Tye relies on Kubernetes Services. Each project gets its own Service, and the environment variables can refer to the hostname mapped to the service.
|
||||
|
||||
This allows service discovery for URIs to work very simply in a deployed application.
|
||||
|
||||
---
|
||||
|
||||
When an application contains a dependency (like Redis, or a Database), Tye will use Kubernetes Secret objects to store the connection string or URI.
|
||||
|
||||
Tye will look for an existing secret based on the service and binding names. If the secret already exists then deployment will proceed.
|
||||
|
||||
If the secret does not exist, then Tye will prompt (in interactive mode) for the connection string or URI value. Based on whether it's a connection string or URI, Tye will create a secret like one of the following.
|
||||
|
||||
Example secret for a URI:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: binding-production-rabbit-secret
|
||||
type: Opaque
|
||||
stringData:
|
||||
protocol: amqp
|
||||
host: rabbitmq
|
||||
port: 5672
|
||||
```
|
||||
|
||||
Example secret for a connection string:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: binding-production-redis-secret
|
||||
type: Opaque
|
||||
stringData:
|
||||
connectionstring: <redacted>
|
||||
```
|
||||
|
||||
Creating the secret is a one-time operation, and Tye will only prompt for it if it does not already exist. If desired you can use standard `kubectl` commands to update values or delete the secret and force it to be recreated.
|
||||
|
||||
To get these values into the application, Tye will use Kubernetes volume mounts to create files on disk inside the container. Currrently these files will be placed in `/var/tye/bindings/<service>-<binding>/` and will use the environment-variable naming scheme described above.
|
||||
|
||||
Inside the application, adding the Tye secrets provider will add these to the configuration.
|
||||
|
||||
```C#
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureAppConfiguration(config =>
|
||||
{
|
||||
// Add Tye secrets
|
||||
config.AddTyeSecrets();
|
||||
})
|
||||
.ConfigureWebHostDefaults(web =>
|
||||
{
|
||||
web.UseStartup<Startup>();
|
||||
});
|
||||
```
|
||||
|
||||
Tye will also inject the `TYE_SECRETS_PATH` environment variable with the value `/var/tye/bindings/`. This is done to avoid hardcoding a file-system path in the configuration provider.
|
|
@ -5,4 +5,8 @@
|
|||
<RootNamespace>Backend</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(TyeLibrariesPath)\Microsoft.Tye.Extensions.Configuration\Microsoft.Tye.Extensions.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.Configuration
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static string? GetServiceHost(this IConfiguration configuration, string name)
|
||||
{
|
||||
return configuration[$"service:{name}:host"];
|
||||
}
|
||||
|
||||
public static Uri? GetServiceUri(this IConfiguration configuration, string name)
|
||||
{
|
||||
var host = configuration[$"service:{name}:host"];
|
||||
var port = configuration[$"service:{name}:port"];
|
||||
var protocol = configuration[$"service:{name}:protocol"] ?? "http";
|
||||
|
||||
if (string.IsNullOrEmpty(host) || port == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Uri(protocol + "://" + host + ":" + port + "/");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,7 +49,7 @@ namespace Frontend
|
|||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
var uri = Configuration.GetServiceUri("backend");
|
||||
var uri = Configuration.GetServiceUri("backend")!;
|
||||
|
||||
logger.LogInformation("Backend URL: {BackendUrl}", uri);
|
||||
|
||||
|
@ -63,16 +63,13 @@ namespace Frontend
|
|||
var bytes = await httpClient.GetByteArrayAsync("/");
|
||||
var backendInfo = JsonSerializer.Deserialize<BackendInfo>(bytes, options);
|
||||
|
||||
var backendHost = Configuration.GetServiceHost("backend");
|
||||
var addresses = await Dns.GetHostAddressesAsync(backendHost);
|
||||
|
||||
await context.Response.WriteAsync($"Frontend Listening IP: {context.Connection.LocalIpAddress}{Environment.NewLine}");
|
||||
await context.Response.WriteAsync($"Frontend Hostname: {Dns.GetHostName()}{Environment.NewLine}");
|
||||
await context.Response.WriteAsync($"EnvVar Configuration value: {Configuration["App:Value"]}{Environment.NewLine}");
|
||||
|
||||
await context.Response.WriteAsync($"Backend Listening IP: {backendInfo.IP}{Environment.NewLine}");
|
||||
await context.Response.WriteAsync($"Backend Hostname: {backendInfo.Hostname}{Environment.NewLine}");
|
||||
|
||||
var addresses = await Dns.GetHostAddressesAsync(uri.Host);
|
||||
await context.Response.WriteAsync($"Backend Host Addresses: {string.Join(", ", addresses.Select(a => a.ToString()))}");
|
||||
});
|
||||
|
||||
|
|
|
@ -5,4 +5,8 @@
|
|||
<RootNamespace>Frontend</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(TyeLibrariesPath)\Microsoft.Tye.Extensions.Configuration\Microsoft.Tye.Extensions.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name: mongo-sample
|
||||
services:
|
||||
- name: mongo
|
||||
image: mongo
|
||||
|
|
|
@ -30,18 +30,8 @@ namespace Backend
|
|||
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
AmqpTcpEndpoint endpoint;
|
||||
var connectionString = Configuration["connectionstring:rabbit"];
|
||||
if (connectionString == null)
|
||||
{
|
||||
var host = Configuration["service:rabbit:host"];
|
||||
var port = int.Parse(Configuration["service:rabbit:port"]);
|
||||
endpoint = new AmqpTcpEndpoint(host, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint = new AmqpTcpEndpoint(new Uri(connectionString));
|
||||
}
|
||||
var uri = Configuration.GetServiceUri("rabbit");
|
||||
var endpoint = new AmqpTcpEndpoint(uri);
|
||||
|
||||
var factory = new ConnectionFactory()
|
||||
{
|
||||
|
|
|
@ -40,11 +40,15 @@ spec:
|
|||
port: 5672
|
||||
targetPort: 5672
|
||||
...
|
||||
#
|
||||
# Use the URI amqp://rabbitmq:5672 when prompted. Tye will create something like:
|
||||
# ---
|
||||
# apiVersion: v1
|
||||
# kind: Secret
|
||||
# metadata:
|
||||
# name: binding-production-rabbit-rabbit-secret
|
||||
# name: binding-production-rabbit-secret
|
||||
# type: Opaque
|
||||
# stringData:
|
||||
# connectionstring: amqp://rabbitmq:5672
|
||||
# protocol: amqp
|
||||
# host: rabbitmq
|
||||
# port: 5672
|
|
@ -1,25 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.Configuration
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static Uri? GetServiceUri(this IConfiguration configuration, string name)
|
||||
{
|
||||
var host = configuration[$"service:{name}:host"];
|
||||
var port = configuration[$"service:{name}:port"];
|
||||
var protocol = configuration[$"service:{name}:protocol"] ?? "http";
|
||||
|
||||
if (string.IsNullOrEmpty(host) || port == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Uri(protocol + "://" + host + ":" + port + "/");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,8 +17,6 @@ namespace Frontend
|
|||
public static void Main(string[] args)
|
||||
{
|
||||
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
|
||||
GrpcClientFactory.AllowUnencryptedHttp2 = true;
|
||||
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,11 @@ namespace Frontend
|
|||
services.AddRazorPages();
|
||||
|
||||
var address = Configuration.GetServiceUri("backend");
|
||||
services.AddSingleton(_ => GrpcChannel.ForAddress(address!).CreateGrpcService<IOrderService>());
|
||||
services.AddSingleton(_ =>
|
||||
{
|
||||
GrpcClientFactory.AllowUnencryptedHttp2 = true;
|
||||
return GrpcChannel.ForAddress(address!).CreateGrpcService<IOrderService>();
|
||||
});
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
|
|
|
@ -37,7 +37,7 @@ spec:
|
|||
secretName: binding-production-rabbit-rabbit-secret
|
||||
items:
|
||||
- key: connectionstring
|
||||
path: CONNECTIONSTRING__RABBIT
|
||||
path: CONNECTIONSTRINGS__RABBIT
|
||||
...
|
||||
---
|
||||
kind: Service
|
||||
|
@ -97,7 +97,7 @@ spec:
|
|||
secretName: binding-production-rabbit-rabbit-secret
|
||||
items:
|
||||
- key: connectionstring
|
||||
path: CONNECTIONSTRING__RABBIT
|
||||
path: CONNECTIONSTRINGS__RABBIT
|
||||
...
|
||||
---
|
||||
kind: Service
|
||||
|
@ -157,5 +157,5 @@ spec:
|
|||
secretName: binding-production-rabbit-rabbit-secret
|
||||
items:
|
||||
- key: connectionstring
|
||||
path: CONNECTIONSTRING__RABBIT
|
||||
path: CONNECTIONSTRINGS__RABBIT
|
||||
...
|
||||
|
|
|
@ -16,4 +16,5 @@ services:
|
|||
- name: rabbit
|
||||
image: rabbitmq:3-management
|
||||
bindings:
|
||||
- port: 5672
|
||||
- protocol: amqp
|
||||
port: 5672
|
||||
|
|
|
@ -65,18 +65,8 @@ namespace Worker
|
|||
{
|
||||
try
|
||||
{
|
||||
AmqpTcpEndpoint endpoint;
|
||||
var connectionString = _configuration["connectionstring:rabbit"];
|
||||
if (connectionString == null)
|
||||
{
|
||||
var host = _configuration["service:rabbit:host"];
|
||||
var port = int.Parse(_configuration["service:rabbit:port"]);
|
||||
endpoint = new AmqpTcpEndpoint(host, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint = new AmqpTcpEndpoint(new Uri(connectionString));
|
||||
}
|
||||
var uri = _configuration.GetServiceUri("rabbit");
|
||||
var endpoint = new AmqpTcpEndpoint(uri);
|
||||
|
||||
var factory = new ConnectionFactory()
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@ services:
|
|||
image: redis
|
||||
bindings:
|
||||
- port: 6379
|
||||
connectionString: ${host}:${port}
|
||||
- name: redis-cli
|
||||
image: redis
|
||||
args: "redis-cli -h redis MONITOR"
|
|
@ -1,13 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Results
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static string GetUri(this IConfiguration configuration, string name)
|
||||
{
|
||||
return $"http://{configuration[$"service:{name}:host"]}:{configuration[$"service:{name}:port"]}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@
|
|||
try
|
||||
{
|
||||
var connection = new HubConnectionBuilder()
|
||||
.WithUrl(Configuration.GetUri("worker")+ "/resultsHub")
|
||||
.WithUrl(Configuration.GetServiceUri("worker")!.AbsoluteUri + "resultsHub")
|
||||
.WithAutomaticReconnect()
|
||||
.Build();
|
||||
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="ChartJs.Blazor" Version="1.0.3" />
|
||||
<PackageReference Include="dapper" Version="2.0.30" />
|
||||
<PackageReference Include="microsoft.aspnetcore.signalr.client" Version="3.1.2" />
|
||||
<PackageReference Include="microsoft.aspnetcore.signalr.client" Version="3.1.3" />
|
||||
<PackageReference Include="npgsql" Version="4.1.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(TyeLibrariesPath)\Microsoft.Tye.Extensions.Configuration\Microsoft.Tye.Extensions.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -6,14 +6,16 @@ services:
|
|||
image: redis
|
||||
bindings:
|
||||
- port: 6379
|
||||
connectionString: ${host}:${port}
|
||||
- name: worker
|
||||
project: worker/worker.csproj
|
||||
- name: postgres
|
||||
image: postgres
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: "pass@word1"
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: "pass@word1"
|
||||
bindings:
|
||||
- port: 5432
|
||||
- port: 5432
|
||||
connectionString: Server=${host};Port=${port};User Id=postgres;Password=${env:POSTGRES_PASSWORD};
|
||||
- name: results
|
||||
project: results/results.csproj
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Vote
|
|||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var connectionString = Configuration["connectionstring:redis"] ?? $"{Configuration["service:redis:host"]}:{Configuration["service:redis:port"]}";
|
||||
var connectionString = Configuration.GetConnectionString("redis");
|
||||
services.AddRazorPages();
|
||||
services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(connectionString));
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Worker
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static string GetSqlConnectionString(this IConfiguration configuration)
|
||||
{
|
||||
return configuration["connectionstring:postgres"] ?? $"Server={configuration["service:postgres:host"]};Port={configuration["service:postgres:port"]};User Id=postgres;Password=pass@word1;";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ namespace Worker
|
|||
[HttpGet("/results")]
|
||||
public async Task<IEnumerable<VoteCount>> Get()
|
||||
{
|
||||
using (var connection = new NpgsqlConnection(_configuration.GetSqlConnectionString()))
|
||||
using (var connection = new NpgsqlConnection(_configuration.GetConnectionString("postgres")))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ namespace Worker
|
|||
|
||||
try
|
||||
{
|
||||
using (var connection = new NpgsqlConnection(_configuration.GetSqlConnectionString()))
|
||||
using (var redisConnection = ConnectionMultiplexer.Connect(GetRedisConnectionString(_configuration)))
|
||||
using (var connection = new NpgsqlConnection(_configuration.GetConnectionString("postgres")))
|
||||
using (var redisConnection = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("redis")))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
|
||||
|
@ -100,16 +100,5 @@ namespace Worker
|
|||
Id VARCHAR(255) NOT NULL UNIQUE,
|
||||
Vote VARCHAR(255) NOT NULL);");
|
||||
}
|
||||
|
||||
private string GetRedisConnectionString(IConfiguration configuration)
|
||||
{
|
||||
var connectionString = configuration["connectionstring:redis"];
|
||||
if (connectionString != null)
|
||||
{
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
return $"{configuration["service:redis:host"]}:{configuration["service:redis:port"]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<IsPackable>true</IsPackable>
|
||||
<DebugType>Embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -58,20 +58,22 @@ namespace Microsoft.Tye
|
|||
continue;
|
||||
}
|
||||
|
||||
|
||||
foreach (var binding in other.Bindings)
|
||||
{
|
||||
// The other thing is a project, and will be deployed along with this
|
||||
// service.
|
||||
var configName =
|
||||
(binding.Name is null || binding.Name == other.Name) ?
|
||||
other.Name.ToUpperInvariant() :
|
||||
$"{other.Name.ToUpperInvariant()}__{binding.Name.ToUpperInvariant()}";
|
||||
if (other is ProjectServiceBuilder)
|
||||
{
|
||||
// The other thing is a project, and will be deployed along with this
|
||||
// service.
|
||||
var configName =
|
||||
(binding.Name is null || binding.Name == other.Name) ?
|
||||
other.Name.ToUpperInvariant() :
|
||||
$"{other.Name.ToUpperInvariant()}__{binding.Name.ToUpperInvariant()}";
|
||||
|
||||
if (!string.IsNullOrEmpty(binding.ConnectionString))
|
||||
{
|
||||
// Special case for connection strings
|
||||
bindings.Bindings.Add(new EnvironmentVariableInputBinding($"CONNECTIONSTRING__{configName}", binding.ConnectionString));
|
||||
bindings.Bindings.Add(new EnvironmentVariableInputBinding($"CONNECTIONSTRINGS__{configName}", binding.ConnectionString));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -107,11 +109,22 @@ namespace Microsoft.Tye
|
|||
else
|
||||
{
|
||||
// The other service is not a project, so we'll use secrets.
|
||||
bindings.Bindings.Add(new SecretInputBinding(
|
||||
name: $"binding-{Environment}-{other.Name}-{binding.Name ?? other.Name}-secret",
|
||||
filename: $"CONNECTIONSTRING__{configName}",
|
||||
other,
|
||||
binding));
|
||||
if (!string.IsNullOrEmpty(binding.ConnectionString))
|
||||
{
|
||||
bindings.Bindings.Add(new SecretConnctionStringInputBinding(
|
||||
name: $"binding-{Environment}-{(binding.Name == null ? other.Name.ToLowerInvariant() : (other.Name.ToLowerInvariant() + "-" + binding.Name.ToLowerInvariant()))}-secret",
|
||||
other,
|
||||
binding,
|
||||
filename: $"CONNECTIONSTRINGS__{(binding.Name == null ? other.Name.ToUpperInvariant() : (other.Name.ToUpperInvariant() + "__" + binding.Name.ToUpperInvariant()))}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
bindings.Bindings.Add(new SecretUrlInputBinding(
|
||||
name: $"binding-{Environment}-{(binding.Name == null ? other.Name.ToLowerInvariant() : (other.Name.ToLowerInvariant() + "-" + binding.Name.ToLowerInvariant()))}-secret",
|
||||
other,
|
||||
binding,
|
||||
filenameBase: $"SERVICE__{(binding.Name == null ? other.Name.ToUpperInvariant() : (other.Name.ToUpperInvariant() + "__" + binding.Name.ToUpperInvariant()))}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,25 +27,49 @@ namespace Microsoft.Tye
|
|||
public string Value { get; }
|
||||
}
|
||||
|
||||
public sealed class SecretInputBinding : InputBinding
|
||||
public abstract class SecretInputBinding : InputBinding
|
||||
{
|
||||
public SecretInputBinding(string name, string filename, ServiceBuilder service, BindingBuilder binding)
|
||||
protected SecretInputBinding(string name, ServiceBuilder service, BindingBuilder binding)
|
||||
{
|
||||
Name = name;
|
||||
Filename = filename;
|
||||
Service = service;
|
||||
Binding = binding;
|
||||
}
|
||||
|
||||
// Used to generate a kubernetes secret
|
||||
public string Name { get; }
|
||||
public string? Value { get; }
|
||||
|
||||
// Used to map the secret to a key that ASP.NET Core understands
|
||||
public string Filename { get; }
|
||||
|
||||
// Used for informational purposes
|
||||
public ServiceBuilder Service { get; }
|
||||
public BindingBuilder Binding { get; }
|
||||
}
|
||||
|
||||
public sealed class SecretConnctionStringInputBinding : SecretInputBinding
|
||||
{
|
||||
public SecretConnctionStringInputBinding(string name, ServiceBuilder service, BindingBuilder binding, string filename)
|
||||
: base(name, service, binding)
|
||||
{
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
// Used to generate a kubernetes secret
|
||||
public string? Value { get; }
|
||||
|
||||
// Used to map the secret to a key that ASP.NET Core understands
|
||||
public string Filename { get; }
|
||||
}
|
||||
|
||||
public sealed class SecretUrlInputBinding : SecretInputBinding
|
||||
{
|
||||
public SecretUrlInputBinding(string name, ServiceBuilder service, BindingBuilder binding, string filenameBase)
|
||||
: base(name, service, binding)
|
||||
{
|
||||
FilenameBase = filenameBase;
|
||||
}
|
||||
|
||||
// Used to generate a kubernetes secret
|
||||
public string? Value { get; }
|
||||
|
||||
// Used to map the secret to keys that ASP.NET Core understands
|
||||
public string FilenameBase { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -232,8 +232,8 @@ namespace Microsoft.Tye
|
|||
var volumeMount = new YamlMappingNode();
|
||||
volumeMounts.Add(volumeMount);
|
||||
|
||||
volumeMount.Add("name", $"{binding.Service.Name}-{binding.Binding.Name ?? binding.Service.Name}");
|
||||
volumeMount.Add("mountPath", $"/var/tye/bindings/{binding.Service.Name}-{binding.Binding.Name ?? binding.Service.Name}");
|
||||
volumeMount.Add("name", $"binding-{(binding.Binding.Name == null ? binding.Service.Name.ToLowerInvariant() : (binding.Service.Name.ToLowerInvariant() + "-" + binding.Binding.Name.ToLowerInvariant()))}");
|
||||
volumeMount.Add("mountPath", $"/var/tye/bindings/{(binding.Binding.Name == null ? binding.Service.Name.ToLowerInvariant() : (binding.Service.Name.ToLowerInvariant() + "-" + binding.Binding.Name.ToLowerInvariant()))}");
|
||||
volumeMount.Add("readOnly", "true");
|
||||
}
|
||||
}
|
||||
|
@ -267,15 +267,15 @@ namespace Microsoft.Tye
|
|||
var volumes = new YamlSequenceNode();
|
||||
spec.Add("volumes", volumes);
|
||||
|
||||
foreach (var binding in bindings.Bindings.OfType<SecretInputBinding>())
|
||||
foreach (var binding in bindings.Bindings.OfType<SecretConnctionStringInputBinding>())
|
||||
{
|
||||
var volume = new YamlMappingNode();
|
||||
volumes.Add(volume);
|
||||
volume.Add("name", $"{binding.Service.Name}-{binding.Binding.Name ?? binding.Service.Name}");
|
||||
volume.Add("name", $"binding-{(binding.Binding.Name == null ? binding.Service.Name.ToLowerInvariant() : (binding.Service.Name.ToLowerInvariant() + "-" + binding.Binding.Name.ToLowerInvariant()))}");
|
||||
|
||||
var secret = new YamlMappingNode();
|
||||
volume.Add("secret", secret);
|
||||
secret.Add("secretName", binding.Name!);
|
||||
secret.Add("secretName", binding.Name);
|
||||
|
||||
var items = new YamlSequenceNode();
|
||||
secret.Add("items", items);
|
||||
|
@ -285,6 +285,35 @@ namespace Microsoft.Tye
|
|||
item.Add("key", "connectionstring");
|
||||
item.Add("path", binding.Filename);
|
||||
}
|
||||
|
||||
foreach (var binding in bindings.Bindings.OfType<SecretUrlInputBinding>())
|
||||
{
|
||||
var volume = new YamlMappingNode();
|
||||
volumes.Add(volume);
|
||||
volume.Add("name", $"binding-{(binding.Binding.Name == null ? binding.Service.Name.ToLowerInvariant() : (binding.Service.Name.ToLowerInvariant() + "-" + binding.Binding.Name.ToLowerInvariant()))}");
|
||||
|
||||
var secret = new YamlMappingNode();
|
||||
volume.Add("secret", secret);
|
||||
secret.Add("secretName", binding.Name);
|
||||
|
||||
var items = new YamlSequenceNode();
|
||||
secret.Add("items", items);
|
||||
|
||||
var item = new YamlMappingNode();
|
||||
items.Add(item);
|
||||
item.Add("key", "protocol");
|
||||
item.Add("path", binding.FilenameBase + "__PROTOCOL");
|
||||
|
||||
item = new YamlMappingNode();
|
||||
items.Add(item);
|
||||
item.Add("key", "host");
|
||||
item.Add("path", binding.FilenameBase + "__HOST");
|
||||
|
||||
item = new YamlMappingNode();
|
||||
items.Add(item);
|
||||
item.Add("key", "port");
|
||||
item.Add("path", binding.FilenameBase + "__PORT");
|
||||
}
|
||||
}
|
||||
|
||||
return new KubernetesDeploymentOutput(project.Name, new YamlDocument(root));
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.CommandLine.Invocation;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using k8s;
|
||||
|
@ -83,7 +83,7 @@ namespace Microsoft.Tye
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!Interactive)
|
||||
if (!Interactive && secretInputBinding is SecretConnctionStringInputBinding)
|
||||
{
|
||||
throw new CommandException(
|
||||
$"The secret '{secretInputBinding.Name}' used for service '{secretInputBinding.Service.Name}' is missing from the deployment environment. " +
|
||||
|
@ -92,20 +92,73 @@ namespace Microsoft.Tye
|
|||
$"kubectl create secret generic {secretInputBinding.Name} --from-literal=connectionstring=<value>");
|
||||
}
|
||||
|
||||
// If we get here then we should create the secret.
|
||||
var text = output.Prompt($"Enter the connection string to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true);
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
if (!Interactive && secretInputBinding is SecretUrlInputBinding)
|
||||
{
|
||||
output.WriteAlways($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created.");
|
||||
output.WriteAlways($"Manually create a secret with:");
|
||||
output.WriteAlways($"kubectl create secret generic {secretInputBinding.Name} --from-literal=connectionstring=<value>");
|
||||
continue;
|
||||
throw new CommandException(
|
||||
$"The secret '{secretInputBinding.Name}' used for service '{secretInputBinding.Service.Name}' is missing from the deployment environment. " +
|
||||
$"Rerun the command with --interactive to specify the value interactively, or with --force to skip validation. Alternatively " +
|
||||
$"use the following command to manually create the secret." + System.Environment.NewLine +
|
||||
$"kubectl create secret generic {secretInputBinding.Name} --from-literal=protocol=<value> --from-literal=host=<value> --from-literal=port=<value>");
|
||||
}
|
||||
|
||||
var secret = new V1Secret(type: "Opaque", stringData: new Dictionary<string, string>()
|
||||
V1Secret secret;
|
||||
if (secretInputBinding is SecretConnctionStringInputBinding)
|
||||
{
|
||||
{ "connectionstring", text },
|
||||
});
|
||||
// If we get here then we should create the secret.
|
||||
var text = output.Prompt($"Enter the connection string to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true);
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
output.WriteAlwaysLine($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created.");
|
||||
output.WriteAlwaysLine($"Manually create a secret with:");
|
||||
output.WriteAlwaysLine($"kubectl create secret generic {secretInputBinding.Name} --from-literal=connectionstring=<value>");
|
||||
continue;
|
||||
}
|
||||
|
||||
secret = new V1Secret(type: "Opaque", stringData: new Dictionary<string, string>()
|
||||
{
|
||||
{ "connectionstring", text },
|
||||
});
|
||||
}
|
||||
else if (secretInputBinding is SecretUrlInputBinding)
|
||||
{
|
||||
// If we get here then we should create the secret.
|
||||
string text;
|
||||
Uri? uri = null;
|
||||
while (true)
|
||||
{
|
||||
text = output.Prompt($"Enter the URI to use for service '{secretInputBinding.Service.Name}'", allowEmpty: true);
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
break; // skip
|
||||
}
|
||||
else if (Uri.TryCreate(text, UriKind.Absolute, out uri))
|
||||
{
|
||||
break; // success
|
||||
}
|
||||
|
||||
output.WriteAlwaysLine($"Invalid URI: '{text}'");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
output.WriteAlwaysLine($"Skipping creation of secret for '{secretInputBinding.Service.Name}'. This may prevent creation of pods until secrets are created.");
|
||||
output.WriteAlwaysLine($"Manually create a secret with:");
|
||||
output.WriteAlwaysLine($"kubectl create secret generic {secretInputBinding.Name} -from-literal=protocol=<value> --from-literal=host=<value> --from-literal=port=<value>");
|
||||
continue;
|
||||
}
|
||||
|
||||
secret = new V1Secret(type: "Opaque", stringData: new Dictionary<string, string>()
|
||||
{
|
||||
{ "protocol", uri!.Scheme },
|
||||
{ "host", uri!.Host },
|
||||
{ "port", uri!.Port.ToString(CultureInfo.InvariantCulture) },
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unknown Secret type: " + secretInputBinding);
|
||||
}
|
||||
|
||||
secret.Metadata = new V1ObjectMeta();
|
||||
secret.Metadata.Name = secretInputBinding.Name;
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.Configuration
|
||||
{
|
||||
public static class TyeConfigurationExtensions
|
||||
{
|
||||
public static Uri? GetServiceUri(this IConfiguration configuration, string name, string? binding = null)
|
||||
{
|
||||
var key = GetKey(name, binding);
|
||||
|
||||
var host = configuration[$"service:{key}:host"];
|
||||
var port = configuration[$"service:{key}:port"];
|
||||
var protocol = configuration[$"service:{key}:protocol"] ?? "http";
|
||||
|
||||
if (string.IsNullOrEmpty(host) || port == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Uri(protocol + "://" + host + ":" + port + "/");
|
||||
}
|
||||
|
||||
public static string? GetConnectionString(this IConfiguration configuration, string name, string? binding)
|
||||
{
|
||||
if (binding == null)
|
||||
{
|
||||
return configuration.GetConnectionString(name);
|
||||
}
|
||||
|
||||
var key = GetKey(name, binding);
|
||||
var connectionString = configuration[$"connectionstrings:{key}"];
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
private static string GetKey(string name, string? binding)
|
||||
{
|
||||
return binding == null ? name : $"{name}:{binding}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.Extensions.Configuration
|
||||
|
@ -18,34 +17,26 @@ namespace Microsoft.Extensions.Configuration
|
|||
/// Adds Tye's secrets to <see cref="IConfiguration" />.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IConfigurationBuilder" />.</param>
|
||||
/// <param name="configure">A delegate for additional configuration.</param>
|
||||
/// <returns>The <see cref="IConfigurationBuilder" />.</returns>
|
||||
/// <remarks>
|
||||
/// The environment variable <c>TYE_SECRETS_PATH</c> is used to populate the directory used by secrets.
|
||||
/// When the environment variable is specified, and the specified directory exists, then the value of
|
||||
/// <see cref="TyeSecretsConfigurationSource.FileProvider" /> will be non-null.
|
||||
/// </remarks>
|
||||
public static IConfigurationBuilder AddTyeSecrets(this IConfigurationBuilder builder, Action<TyeSecretsConfigurationSource>? configure = null)
|
||||
public static IConfigurationBuilder AddTyeSecrets(this IConfigurationBuilder builder)
|
||||
{
|
||||
TyeSecretsConfigurationSource source;
|
||||
var secretsDirectory = Environment.GetEnvironmentVariable(TyeSecretsConfigurationSource.TyeSecretsPathEnvironmentVariable);
|
||||
if (secretsDirectory == null || !Directory.Exists(secretsDirectory))
|
||||
if (Directory.Exists(secretsDirectory))
|
||||
{
|
||||
source = new TyeSecretsConfigurationSource()
|
||||
foreach (var child in Directory.EnumerateDirectories(secretsDirectory))
|
||||
{
|
||||
FileProvider = null,
|
||||
};
|
||||
var source = new TyeSecretsConfigurationSource()
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(child),
|
||||
};
|
||||
builder.Add(source);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
source = new TyeSecretsConfigurationSource()
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(secretsDirectory),
|
||||
};
|
||||
}
|
||||
|
||||
configure?.Invoke(source);
|
||||
builder.Add(source);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
|
|
@ -281,7 +281,7 @@ namespace Microsoft.Tye.Hosting
|
|||
|
||||
foreach (var pair in environment)
|
||||
{
|
||||
environmentArguments += $"-e {pair.Key}={pair.Value} ";
|
||||
environmentArguments += $"-e \"{pair.Key}={pair.Value}\" ";
|
||||
}
|
||||
|
||||
foreach (var volumeMapping in docker.VolumeMappings)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -30,6 +31,8 @@ namespace Microsoft.Tye.Hosting.Model
|
|||
|
||||
public void PopulateEnvironment(Service service, Action<string, string> set, string defaultHost = "localhost")
|
||||
{
|
||||
var bindings = ComputeBindings(service, defaultHost);
|
||||
|
||||
if (service.Description.Configuration != null)
|
||||
{
|
||||
// Inject normal configuration
|
||||
|
@ -41,36 +44,36 @@ namespace Microsoft.Tye.Hosting.Model
|
|||
}
|
||||
else if (pair.Source is object)
|
||||
{
|
||||
set(pair.Name, GetValueFromBinding(pair.Source));
|
||||
set(pair.Name, GetValueFromBinding(bindings, pair.Source));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string GetValueFromBinding(EnvironmentVariableSource source)
|
||||
// Inject dependency information
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
if (!Services.TryGetValue(source.Service, out var service))
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find service '{source.Service}'.");
|
||||
}
|
||||
SetBinding(binding);
|
||||
}
|
||||
|
||||
var binding = service.Description.Bindings.Where(b => b.Name == source.Binding).FirstOrDefault();
|
||||
static string GetValueFromBinding(List<EffectiveBinding> bindings, EnvironmentVariableSource source)
|
||||
{
|
||||
var binding = bindings.Where(b => b.Service == b.Service && b.Name == source.Binding).FirstOrDefault();
|
||||
if (binding == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find binding '{source.Binding}' for service '{source.Service}'.");
|
||||
}
|
||||
|
||||
// TODO finish
|
||||
if (source.Kind == EnvironmentVariableSource.SourceKind.Port && binding.Port != null)
|
||||
{
|
||||
return binding.Port.Value.ToString();
|
||||
}
|
||||
else if (source.Kind == EnvironmentVariableSource.SourceKind.Host)
|
||||
{
|
||||
return binding.Host ?? defaultHost;
|
||||
return binding.Host;
|
||||
}
|
||||
else if (source.Kind == EnvironmentVariableSource.SourceKind.Url)
|
||||
{
|
||||
return $"{binding.Protocol ?? "http"}://{binding.Host ?? defaultHost}" + (binding.Port.HasValue ? (":" + binding.Port) : string.Empty);
|
||||
return $"{binding.Protocol ?? "http"}://{binding.Host}" + (binding.Port.HasValue ? (":" + binding.Port) : string.Empty);
|
||||
}
|
||||
else if (source.Kind == EnvironmentVariableSource.SourceKind.ConnectionString && binding.ConnectionString != null)
|
||||
{
|
||||
|
@ -80,59 +83,80 @@ namespace Microsoft.Tye.Hosting.Model
|
|||
throw new InvalidOperationException($"Unable to resolve the desired value '{source.Kind}' from binding '{source.Binding}' for service '{source.Service}'.");
|
||||
}
|
||||
|
||||
void SetBinding(ServiceDescription targetService, ServiceBinding b)
|
||||
void SetBinding(EffectiveBinding binding)
|
||||
{
|
||||
var serviceName = targetService.Name.ToUpper();
|
||||
var serviceName = binding.Service.ToUpper();
|
||||
var configName = "";
|
||||
var envName = "";
|
||||
|
||||
if (string.IsNullOrEmpty(b.Name))
|
||||
if (string.IsNullOrEmpty(binding.Name))
|
||||
{
|
||||
configName = serviceName;
|
||||
envName = serviceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
configName = $"{serviceName.ToUpper()}__{b.Name.ToUpper()}";
|
||||
envName = $"{serviceName.ToUpper()}_{b.Name.ToUpper()}";
|
||||
configName = $"{serviceName.ToUpper()}__{binding.Name.ToUpper()}";
|
||||
envName = $"{serviceName.ToUpper()}_{binding.Name.ToUpper()}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(b.ConnectionString))
|
||||
if (!string.IsNullOrEmpty(binding.ConnectionString))
|
||||
{
|
||||
// Special case for connection strings
|
||||
set($"CONNECTIONSTRING__{configName}", b.ConnectionString);
|
||||
var connectionString = TokenReplacement.ReplaceValues(binding.ConnectionString, binding, bindings);
|
||||
set($"CONNECTIONSTRINGS__{configName}", connectionString);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(b.Protocol))
|
||||
if (!string.IsNullOrEmpty(binding.Protocol))
|
||||
{
|
||||
// IConfiguration specific (double underscore ends up telling the configuration provider to use it as a separator)
|
||||
set($"SERVICE__{configName}__PROTOCOL", b.Protocol);
|
||||
set($"{envName}_SERVICE_PROTOCOL", b.Protocol);
|
||||
set($"SERVICE__{configName}__PROTOCOL", binding.Protocol);
|
||||
set($"{envName}_SERVICE_PROTOCOL", binding.Protocol);
|
||||
}
|
||||
|
||||
if (b.Port != null)
|
||||
if (binding.Port != null)
|
||||
{
|
||||
var port = (service.Description.RunInfo is DockerRunInfo) ? b.ContainerPort ?? b.Port.Value : b.Port.Value;
|
||||
|
||||
set($"SERVICE__{configName}__PORT", port.ToString());
|
||||
set($"{envName}_SERVICE_PORT", port.ToString());
|
||||
set($"SERVICE__{configName}__PORT", binding.Port.Value.ToString(CultureInfo.InvariantCulture));
|
||||
set($"{envName}_SERVICE_PORT", binding.Port.Value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
// Use the container name as the host name if there's a single replica (current limitation)
|
||||
var host = b.Host ?? (service.Description.RunInfo is DockerRunInfo ? targetService.Name : defaultHost);
|
||||
|
||||
set($"SERVICE__{configName}__HOST", host);
|
||||
set($"{envName}_SERVICE_HOST", host);
|
||||
set($"SERVICE__{configName}__HOST", binding.Host);
|
||||
set($"{envName}_SERVICE_HOST", binding.Host);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the list of bindings visible to `service`. This is contextual because details like
|
||||
// whether `service` is a container or not will change the result.
|
||||
private List<EffectiveBinding> ComputeBindings(Service service, string defaultHost)
|
||||
{
|
||||
var bindings = new List<EffectiveBinding>();
|
||||
|
||||
// Inject dependency information
|
||||
foreach (var s in Services.Values)
|
||||
{
|
||||
foreach (var b in s.Description.Bindings)
|
||||
{
|
||||
SetBinding(s.Description, b);
|
||||
var protocol = b.Protocol;
|
||||
var host = b.Host ?? (service.Description.RunInfo is DockerRunInfo ? s.Description.Name : defaultHost);
|
||||
|
||||
var port = b.Port;
|
||||
if (b.Port is object && service.Description.RunInfo is DockerRunInfo)
|
||||
{
|
||||
port = b.ContainerPort ?? b.Port.Value;
|
||||
}
|
||||
|
||||
bindings.Add(new EffectiveBinding(
|
||||
s.Description.Name,
|
||||
b.Name,
|
||||
protocol,
|
||||
host,
|
||||
port,
|
||||
b.ConnectionString,
|
||||
s.Description.Configuration));
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Tye.Hosting.Model
|
||||
{
|
||||
public class EffectiveBinding
|
||||
{
|
||||
public EffectiveBinding(string service, string? name, string? protocol, string host, int? port, string? connectionString, List<EnvironmentVariable> env)
|
||||
{
|
||||
Service = service;
|
||||
Name = name;
|
||||
Protocol = protocol;
|
||||
Host = host;
|
||||
Port = port;
|
||||
ConnectionString = connectionString;
|
||||
Env = env;
|
||||
}
|
||||
|
||||
public string Service { get; }
|
||||
|
||||
public string? Name { get; }
|
||||
|
||||
public string? Protocol { get; }
|
||||
|
||||
public string Host { get; }
|
||||
|
||||
public int? Port { get; }
|
||||
|
||||
public string? ConnectionString { get; }
|
||||
|
||||
public List<EnvironmentVariable> Env { get; }
|
||||
}
|
||||
}
|
|
@ -44,12 +44,6 @@ namespace Microsoft.Tye.Hosting
|
|||
|
||||
foreach (var binding in service.Description.Bindings)
|
||||
{
|
||||
if (binding.ConnectionString != null)
|
||||
{
|
||||
// Skip if there is a port or if there is a connection string
|
||||
continue;
|
||||
}
|
||||
|
||||
// Auto assign a port
|
||||
if (binding.Port == null)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.Tye.Hosting.Model;
|
||||
|
||||
namespace Microsoft.Tye.Hosting
|
||||
{
|
||||
internal static class TokenReplacement
|
||||
{
|
||||
public static string ReplaceValues(string text, EffectiveBinding binding, List<EffectiveBinding> bindings)
|
||||
{
|
||||
var tokens = GetTokens(text);
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
var replacement = ResolveToken(token, binding, bindings);
|
||||
if (replacement is null)
|
||||
{
|
||||
throw new InvalidOperationException($"No available substitutions found for token '{token}'.");
|
||||
}
|
||||
|
||||
text = text.Replace(token, replacement);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private static HashSet<string> GetTokens(string text)
|
||||
{
|
||||
var tokens = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
var i = 0;
|
||||
while ((i = text.IndexOf("${", i)) != -1)
|
||||
{
|
||||
var start = i;
|
||||
var end = (int?)null;
|
||||
for (; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '}')
|
||||
{
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (end is null)
|
||||
{
|
||||
throw new FormatException($"Value '{text}' contains an unclosed replacement token '{text[start..text.Length]}'.");
|
||||
}
|
||||
|
||||
var token = text[start..(end.Value + 1)];
|
||||
tokens.Add(token);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private static string? ResolveToken(string token, EffectiveBinding binding, List<EffectiveBinding> bindings)
|
||||
{
|
||||
// The language we support for tokens is meant to be pretty DRY. It supports a few different formats:
|
||||
//
|
||||
// - ${host}: only allowed inside a connection string, it can refer to the binding.
|
||||
// - ${env:SOME_VAR}: allowed anywhere. It can refer to any environment variable defined for *this* service.
|
||||
// - ${service:myservice:port}: allowed anywhere. It can refer to the protocol/host/port of bindings.
|
||||
|
||||
var keys = token[2..^1].Split(':');
|
||||
if (keys.Length == 1)
|
||||
{
|
||||
// If there's a single key, it has to be the simple format.
|
||||
return GetValueFromBinding(binding, keys[0]);
|
||||
}
|
||||
else if (keys.Length == 2 && keys[0] == "env")
|
||||
{
|
||||
return GetEnvironmentVariable(binding, keys[1]);
|
||||
}
|
||||
else if (keys.Length == 3 && keys[0] == "service")
|
||||
{
|
||||
binding = bindings.FirstOrDefault(b => b.Service == keys[1] && b.Name == null);
|
||||
return GetValueFromBinding(binding, keys[2]);
|
||||
}
|
||||
else if (keys.Length == 4 && keys[0] == "service")
|
||||
{
|
||||
binding = bindings.FirstOrDefault(b => b.Service == keys[1] && b.Name == keys[2]);
|
||||
return GetValueFromBinding(binding, keys[3]);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
string? GetValueFromBinding(EffectiveBinding binding, string key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
"protocol" => binding.Protocol,
|
||||
"host" => binding.Host,
|
||||
"port" => binding.Port?.ToString(CultureInfo.InvariantCulture),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
static string? GetEnvironmentVariable(EffectiveBinding binding, string key)
|
||||
{
|
||||
var envVar = binding.Env.FirstOrDefault(e => string.Equals(e.Name, key, StringComparison.OrdinalIgnoreCase));
|
||||
return envVar?.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,68 +42,44 @@
|
|||
],
|
||||
"definitions": {
|
||||
"binding": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The binding name.",
|
||||
"type": "string"
|
||||
},
|
||||
"connectionString": {
|
||||
"description": "The connection string.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The binding name.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The binding name.",
|
||||
"type": "string"
|
||||
},
|
||||
"port": {
|
||||
"description": "The binding port.",
|
||||
"type": "integer"
|
||||
},
|
||||
"host": {
|
||||
"description": "The hostname for the binding.",
|
||||
"type": "string"
|
||||
},
|
||||
"protocol": {
|
||||
"description": "The protocol used by the binding",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
"port": {
|
||||
"description": "The binding port.",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The binding name.",
|
||||
"type": "string"
|
||||
},
|
||||
"autoAssignPort": {
|
||||
"description": "Whether to auto-assign a port value.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"protocol": {
|
||||
"description": "The protocol used by the binding",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
"containerPort": {
|
||||
"description": "The port used when running inside a container.",
|
||||
"type": "integer"
|
||||
},
|
||||
"host": {
|
||||
"description": "The hostname for the binding.",
|
||||
"type": "string"
|
||||
},
|
||||
"protocol": {
|
||||
"description": "The protocol used by the binding",
|
||||
"type": "string"
|
||||
},
|
||||
"autoAssignPort": {
|
||||
"description": "Whether to auto-assign a port value.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"connectionString": {
|
||||
"description": "The connection string.",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"env-var": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name" :{
|
||||
"name": {
|
||||
"description": "Environment variable name.",
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -112,7 +88,10 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name", "value"],
|
||||
"required": [
|
||||
"name",
|
||||
"value"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"executable": {
|
||||
|
@ -132,7 +111,7 @@
|
|||
},
|
||||
"env": {
|
||||
"description": "Environment variables to use when launching.",
|
||||
"type" : "array",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/env-var"
|
||||
}
|
||||
|
@ -147,7 +126,7 @@
|
|||
},
|
||||
"bindings": {
|
||||
"description": "The bindings provided by the service.",
|
||||
"type":"array",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/binding"
|
||||
}
|
||||
|
@ -185,7 +164,7 @@
|
|||
},
|
||||
"env": {
|
||||
"description": "Environment variables to use when launching.",
|
||||
"type" : "array",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/env-var"
|
||||
}
|
||||
|
@ -200,7 +179,7 @@
|
|||
},
|
||||
"bindings": {
|
||||
"description": "The bindings provided by the service.",
|
||||
"type":"array",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/binding"
|
||||
}
|
||||
|
@ -224,7 +203,7 @@
|
|||
},
|
||||
"env": {
|
||||
"description": "Environment variables to use when launching.",
|
||||
"type" : "array",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/env-var"
|
||||
}
|
||||
|
@ -243,7 +222,7 @@
|
|||
},
|
||||
"bindings": {
|
||||
"description": "The bindings provided by the service.",
|
||||
"type":"array",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/binding"
|
||||
}
|
||||
|
|
|
@ -210,5 +210,110 @@ namespace E2ETest
|
|||
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipIfDockerNotRunning]
|
||||
public async Task Generate_ConnectionStringDependency()
|
||||
{
|
||||
var applicationName = "generate-connectionstring-dependency";
|
||||
var projectName = "frontend";
|
||||
var environment = "production";
|
||||
|
||||
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
|
||||
|
||||
using var projectDirectory = TestHelpers.CopyTestProjectDirectory(applicationName);
|
||||
|
||||
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
|
||||
|
||||
var outputContext = new OutputContext(sink, Verbosity.Debug);
|
||||
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
|
||||
|
||||
try
|
||||
{
|
||||
await GenerateHost.ExecuteGenerateAsync(outputContext, application, environment, interactive: false);
|
||||
|
||||
// name of application is the folder
|
||||
var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml"));
|
||||
var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.yaml");
|
||||
|
||||
Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines());
|
||||
|
||||
await DockerAssert.AssertImageExistsAsync(output, projectName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipIfDockerNotRunning]
|
||||
public async Task Generate_UriDependency()
|
||||
{
|
||||
var applicationName = "generate-uri-dependency";
|
||||
var projectName = "frontend";
|
||||
var environment = "production";
|
||||
|
||||
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
|
||||
|
||||
using var projectDirectory = TestHelpers.CopyTestProjectDirectory(applicationName);
|
||||
|
||||
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
|
||||
|
||||
var outputContext = new OutputContext(sink, Verbosity.Debug);
|
||||
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
|
||||
|
||||
try
|
||||
{
|
||||
await GenerateHost.ExecuteGenerateAsync(outputContext, application, environment, interactive: false);
|
||||
|
||||
// name of application is the folder
|
||||
var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml"));
|
||||
var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.yaml");
|
||||
|
||||
Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines());
|
||||
|
||||
await DockerAssert.AssertImageExistsAsync(output, projectName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipIfDockerNotRunning]
|
||||
public async Task Generate_NamedBinding()
|
||||
{
|
||||
var applicationName = "generate-named-binding";
|
||||
var projectName = "frontend";
|
||||
var environment = "production";
|
||||
|
||||
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
|
||||
|
||||
using var projectDirectory = TestHelpers.CopyTestProjectDirectory(applicationName);
|
||||
|
||||
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
|
||||
|
||||
var outputContext = new OutputContext(sink, Verbosity.Debug);
|
||||
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
|
||||
|
||||
try
|
||||
{
|
||||
await GenerateHost.ExecuteGenerateAsync(outputContext, application, environment, interactive: false);
|
||||
|
||||
// name of application is the folder
|
||||
var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, $"{applicationName}-generate-{environment}.yaml"));
|
||||
var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.yaml");
|
||||
|
||||
Assert.Equal(expectedContent.NormalizeNewLines(), content.NormalizeNewLines());
|
||||
|
||||
await DockerAssert.AssertImageExistsAsync(output, projectName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: frontend
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-connectionstring-dependency'
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-connectionstring-dependency'
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
image: frontend:1.0.0
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: 'http://*'
|
||||
- name: PORT
|
||||
value: '80'
|
||||
- name: TYE_SECRETS_PATH
|
||||
value: '/var/tye/bindings/'
|
||||
volumeMounts:
|
||||
- name: binding-dependency
|
||||
mountPath: /var/tye/bindings/dependency
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumes:
|
||||
- name: binding-dependency
|
||||
secret:
|
||||
secretName: binding-production-dependency-secret
|
||||
items:
|
||||
- key: connectionstring
|
||||
path: CONNECTIONSTRINGS__DEPENDENCY
|
||||
...
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: frontend
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-connectionstring-dependency'
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: frontend
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
...
|
|
@ -0,0 +1,65 @@
|
|||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: frontend
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-named-binding'
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-named-binding'
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
image: frontend:1.0.0
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: 'http://*'
|
||||
- name: PORT
|
||||
value: '80'
|
||||
- name: TYE_SECRETS_PATH
|
||||
value: '/var/tye/bindings/'
|
||||
volumeMounts:
|
||||
- name: binding-dependency-mybinding
|
||||
mountPath: /var/tye/bindings/dependency-mybinding
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumes:
|
||||
- name: binding-dependency-mybinding
|
||||
secret:
|
||||
secretName: binding-production-dependency-mybinding-secret
|
||||
items:
|
||||
- key: protocol
|
||||
path: SERVICE__DEPENDENCY__MYBINDING__PROTOCOL
|
||||
- key: host
|
||||
path: SERVICE__DEPENDENCY__MYBINDING__HOST
|
||||
- key: port
|
||||
path: SERVICE__DEPENDENCY__MYBINDING__PORT
|
||||
...
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: frontend
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-named-binding'
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: frontend
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
...
|
|
@ -0,0 +1,65 @@
|
|||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: frontend
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-uri-dependency'
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-uri-dependency'
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
image: frontend:1.0.0
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: 'http://*'
|
||||
- name: PORT
|
||||
value: '80'
|
||||
- name: TYE_SECRETS_PATH
|
||||
value: '/var/tye/bindings/'
|
||||
volumeMounts:
|
||||
- name: binding-dependency
|
||||
mountPath: /var/tye/bindings/dependency
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumes:
|
||||
- name: binding-dependency
|
||||
secret:
|
||||
secretName: binding-production-dependency-secret
|
||||
items:
|
||||
- key: protocol
|
||||
path: SERVICE__DEPENDENCY__PROTOCOL
|
||||
- key: host
|
||||
path: SERVICE__DEPENDENCY__HOST
|
||||
- key: port
|
||||
path: SERVICE__DEPENDENCY__PORT
|
||||
...
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: frontend
|
||||
labels:
|
||||
app.kubernetes.io/name: 'frontend'
|
||||
app.kubernetes.io/part-of: 'generate-uri-dependency'
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: frontend
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
...
|
|
@ -34,18 +34,22 @@ spec:
|
|||
- name: TYE_SECRETS_PATH
|
||||
value: '/var/tye/bindings/'
|
||||
volumeMounts:
|
||||
- name: rabbit-rabbit
|
||||
mountPath: /var/tye/bindings/rabbit-rabbit
|
||||
- name: binding-rabbit
|
||||
mountPath: /var/tye/bindings/rabbit
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 7000
|
||||
volumes:
|
||||
- name: rabbit-rabbit
|
||||
- name: binding-rabbit
|
||||
secret:
|
||||
secretName: binding-production-rabbit-rabbit-secret
|
||||
secretName: binding-production-rabbit-secret
|
||||
items:
|
||||
- key: connectionstring
|
||||
path: CONNECTIONSTRING__RABBIT
|
||||
- key: protocol
|
||||
path: SERVICE__RABBIT__PROTOCOL
|
||||
- key: host
|
||||
path: SERVICE__RABBIT__HOST
|
||||
- key: port
|
||||
path: SERVICE__RABBIT__PORT
|
||||
...
|
||||
---
|
||||
kind: Service
|
||||
|
@ -102,18 +106,22 @@ spec:
|
|||
- name: TYE_SECRETS_PATH
|
||||
value: '/var/tye/bindings/'
|
||||
volumeMounts:
|
||||
- name: rabbit-rabbit
|
||||
mountPath: /var/tye/bindings/rabbit-rabbit
|
||||
- name: binding-rabbit
|
||||
mountPath: /var/tye/bindings/rabbit
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
volumes:
|
||||
- name: rabbit-rabbit
|
||||
- name: binding-rabbit
|
||||
secret:
|
||||
secretName: binding-production-rabbit-rabbit-secret
|
||||
secretName: binding-production-rabbit-secret
|
||||
items:
|
||||
- key: connectionstring
|
||||
path: CONNECTIONSTRING__RABBIT
|
||||
- key: protocol
|
||||
path: SERVICE__RABBIT__PROTOCOL
|
||||
- key: host
|
||||
path: SERVICE__RABBIT__HOST
|
||||
- key: port
|
||||
path: SERVICE__RABBIT__PORT
|
||||
...
|
||||
---
|
||||
kind: Service
|
||||
|
@ -172,14 +180,18 @@ spec:
|
|||
- name: TYE_SECRETS_PATH
|
||||
value: '/var/tye/bindings/'
|
||||
volumeMounts:
|
||||
- name: rabbit-rabbit
|
||||
mountPath: /var/tye/bindings/rabbit-rabbit
|
||||
- name: binding-rabbit
|
||||
mountPath: /var/tye/bindings/rabbit
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: rabbit-rabbit
|
||||
- name: binding-rabbit
|
||||
secret:
|
||||
secretName: binding-production-rabbit-rabbit-secret
|
||||
secretName: binding-production-rabbit-secret
|
||||
items:
|
||||
- key: connectionstring
|
||||
path: CONNECTIONSTRING__RABBIT
|
||||
- key: protocol
|
||||
path: SERVICE__RABBIT__PROTOCOL
|
||||
- key: host
|
||||
path: SERVICE__RABBIT__HOST
|
||||
- key: port
|
||||
path: SERVICE__RABBIT__PORT
|
||||
...
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace frontend
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:28847",
|
||||
"sslPort": 44342
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"frontend": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace frontend
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapGet("/", async context =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello World!");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "frontend", "frontend\frontend.csproj", "{E2BB6C2D-4682-41B2-B044-4C78465B337A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,14 @@
|
|||
# tye application configuration file
|
||||
# read all about it at https://github.com/dotnet/tye
|
||||
#
|
||||
# when you've given us a try, we'd love to know what you think:
|
||||
# https://aka.ms/AA7q20u
|
||||
#
|
||||
name: generate-connectionstring-dependency
|
||||
services:
|
||||
- name: frontend
|
||||
project: frontend/frontend.csproj
|
||||
- name: dependency
|
||||
image: fake-image # not actually used for running
|
||||
bindings:
|
||||
- connectionString: test # need something here to create a binding
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace frontend
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:28847",
|
||||
"sslPort": 44342
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"frontend": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace frontend
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapGet("/", async context =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello World!");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "frontend", "frontend\frontend.csproj", "{E2BB6C2D-4682-41B2-B044-4C78465B337A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,14 @@
|
|||
# tye application configuration file
|
||||
# read all about it at https://github.com/dotnet/tye
|
||||
#
|
||||
# when you've given us a try, we'd love to know what you think:
|
||||
# https://aka.ms/AA7q20u
|
||||
#
|
||||
name: generate-named-binding
|
||||
services:
|
||||
- name: frontend
|
||||
project: frontend/frontend.csproj
|
||||
- name: dependency
|
||||
image: fake-image # not actually used for running
|
||||
bindings:
|
||||
- name: myBinding
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace frontend
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:28847",
|
||||
"sslPort": 44342
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"frontend": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace frontend
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapGet("/", async context =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello World!");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "frontend", "frontend\frontend.csproj", "{E2BB6C2D-4682-41B2-B044-4C78465B337A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E2BB6C2D-4682-41B2-B044-4C78465B337A}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,14 @@
|
|||
# tye application configuration file
|
||||
# read all about it at https://github.com/dotnet/tye
|
||||
#
|
||||
# when you've given us a try, we'd love to know what you think:
|
||||
# https://aka.ms/AA7q20u
|
||||
#
|
||||
name: generate-uri-dependency
|
||||
services:
|
||||
- name: frontend
|
||||
project: frontend/frontend.csproj
|
||||
- name: dependency
|
||||
image: fake-image # not actually used for running
|
||||
bindings:
|
||||
- port: 8001 # need something here to create a binding
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Tye.Extensions.Configuration\Microsoft.Tye.Extensions.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,273 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration.Memory;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Configuration
|
||||
{
|
||||
public class TyeConfigurationExtensionsTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetConnectionString_WithoutBinding_GetsConnectionString()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "connectionstrings:myservice", "expected" }
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetConnectionString("myservice");
|
||||
Assert.Equal("expected", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionString_WithoutBinding_DoesNotCombineHostAndPort()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:host", "example.com" },
|
||||
{ "service:myservice:port", "443" }
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetConnectionString("myservice");
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionString_WithBinding_GetsConnectionString()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "connectionstrings:myservice:http", "expected" }
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetConnectionString("myservice", "http");
|
||||
Assert.Equal("expected", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionString_WithBinding_DoesNotMatchOtherBinding()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "connectionstrings:myservice:https", "expected" }
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetConnectionString("myservice", "http");
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionString_WithBinding_DoesNotMatchDefaultBinding()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "connectionstrings:myservice", "expected" }
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetConnectionString("myservice", "http");
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionString_WithBinding_DoesNotCombineHostAndPort()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:http:host", "example.com" },
|
||||
{ "service:myservice:http:port", "443" }
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetConnectionString("myservice", "http");
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithoutBinding_IgnoresConnectionStringIfSet()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "connectionstrings:myservice", "https://example.com" },
|
||||
{ "service:myservice:protocol", "http" },
|
||||
{ "service:myservice:host", "expected.example.com" },
|
||||
{ "service:myservice:port", "80" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice");
|
||||
Assert.Equal(new Uri("http://expected.example.com"), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithoutBinding_ReturnsNullIfNotFound()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:anotherservice:protocol", "https" },
|
||||
{ "service:anotherservice:host", "expected.example.com" },
|
||||
{ "service:anotherservice:port", "5000" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice");
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithoutBinding_CombinesHostAndPort()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:protocol", "https" },
|
||||
{ "service:myservice:host", "expected.example.com" },
|
||||
{ "service:myservice:port", "5000" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice");
|
||||
Assert.Equal(new Uri("https://expected.example.com:5000"), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithoutBinding_WithHttpAsDefaultProtocol()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:host", "expected.example.com" },
|
||||
{ "service:myservice:port", "5000" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice");
|
||||
Assert.Equal(new Uri("http://expected.example.com:5000"), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithoutBinding_WithoutHost_ReturnsNull()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:protocol", "https" },
|
||||
{ "service:myservice:port", "5000" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice");
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithoutBinding_WithoutPort_ReturnsNull()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:protocol", "https" },
|
||||
{ "service:myservice:host", "example.com" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice");
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_InvalidValues_Throws()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:protocol", "https" },
|
||||
{ "service:myservice:host", "@#*$&$(" },
|
||||
{ "service:myservice:port", "example.com" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
Assert.Throws<UriFormatException>(() => configuration.GetServiceUri("myservice"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithBinding_IgnoresConnectionStringIfSet()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "connectionstrings:myservice:metrics", "https://example.com" },
|
||||
{ "service:myservice:metrics:protocol", "http" },
|
||||
{ "service:myservice:metrics:host", "expected.example.com" },
|
||||
{ "service:myservice:metrics:port", "80" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice", "metrics");
|
||||
Assert.Equal(new Uri("http://expected.example.com"), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithBinding_CombinesHostAndPort()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:metrics:protocol", "https" },
|
||||
{ "service:myservice:metrics:host", "expected.example.com" },
|
||||
{ "service:myservice:metrics:port", "5000" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice", "metrics");
|
||||
Assert.Equal(new Uri("https://expected.example.com:5000"), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceUri_WithBinding_IgnoresDefaultBinding()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
{
|
||||
{ "service:myservice:host", "expected.example.com" },
|
||||
{ "service:myservice:port", "5000" },
|
||||
});
|
||||
|
||||
var configuration = builder.Build();
|
||||
|
||||
var result = configuration.GetServiceUri("myservice", "metrics");
|
||||
Assert.Null(result);
|
||||
}
|
||||
}
|
||||
}
|
15
tye.sln
15
tye.sln
|
@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Tye.Extensions.Co
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Tye.Proxy", "src\Microsoft.Tye.Proxy\Microsoft.Tye.Proxy.csproj", "{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Tye.Extensions.Configuration.Tests", "test\Microsoft.Tye.Extensions.Configuration.Tests\Microsoft.Tye.Extensions.Configuration.Tests.csproj", "{FCE7C889-16D1-42E7-A514-EA096E9D41A7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -157,6 +159,18 @@ Global
|
|||
{2233F4A8-10F9-40A6-BFD3-8D0C37F8359A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{2233F4A8-10F9-40A6-BFD3-8D0C37F8359A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2233F4A8-10F9-40A6-BFD3-8D0C37F8359A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -172,6 +186,7 @@ Global
|
|||
{B07394E4-30A7-429A-BC5A-747B54D5A447} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
|
||||
{7C9021B7-64BA-4DA9-88DA-5BC12A1C6233} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
|
||||
{2233F4A8-10F9-40A6-BFD3-8D0C37F8359A} = {F19B02EB-A372-417A-B2C2-EA0D5A3C76D5}
|
||||
{FCE7C889-16D1-42E7-A514-EA096E9D41A7} = {F19B02EB-A372-417A-B2C2-EA0D5A3C76D5}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D8002603-BB27-4500-BF86-274A8E72D302}
|
||||
|
|
Загрузка…
Ссылка в новой задаче