From b0ffd07503a8ec8783203d4dde5f435296ca90dd Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Fri, 23 Nov 2018 11:03:03 +0100 Subject: [PATCH 01/10] refactored build script --- Dockerfile | 16 ++--- build/Program.cs | 184 ++++++++++++++++++++++++++--------------------- 2 files changed, 109 insertions(+), 91 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7a04977..36b9f63 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,12 @@ FROM microsoft/dotnet:2.1.500-sdk-alpine3.7 AS build ARG MYGET_API_KEY -ARG MINVERBUILDMETADATA RUN apk add --no-cache \ nodejs \ yarn \ libcurl -WORKDIR /src +WORKDIR /app/src COPY ./src/*.sln ./ COPY ./src/*/*.csproj ./ @@ -19,15 +18,15 @@ RUN dotnet restore --runtime=alpine.3.7-x64 COPY ./src . -WORKDIR /docs +WORKDIR /app/docs COPY ./docs/package.json ./docs/yarn.lock ./ -WORKDIR /.git +WORKDIR /app/.git COPY ./.git . -WORKDIR /build +WORKDIR /app/build COPY ./build/build.csproj . @@ -35,15 +34,14 @@ RUN dotnet restore COPY ./build . -WORKDIR / +WORKDIR /app -RUN MINVERBUILDMETADATA=$MINVERBUILDMETADATA \ - MYGET_API_KEY=$MYGET_API_KEY \ +RUN MYGET_API_KEY=$MYGET_API_KEY \ dotnet run --project build/build.csproj FROM microsoft/dotnet:2.1.6-runtime-deps-alpine3.7 AS runtime WORKDIR /app -COPY --from=build /publish ./ +COPY --from=build /app/publish ./ ENTRYPOINT ["/app/SqlStreamStore.HAL.DevServer"] diff --git a/build/Program.cs b/build/Program.cs index 63fba67..58836e5 100644 --- a/build/Program.cs +++ b/build/Program.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using static Bullseye.Targets; using static SimpleExec.Command; @@ -10,112 +11,131 @@ static class Program private const string ArtifactsDir = "artifacts"; private const string PublishDir = "publish"; - private const string Clean = nameof(Clean); - private const string Init = nameof(Init); - private const string GenerateDocumentation = nameof(GenerateDocumentation); - private const string Build = nameof(Build); - private const string RunTests = nameof(RunTests); - private const string Pack = nameof(Pack); - private const string Publish = nameof(Publish); - private const string Push = nameof(Push); + private static readonly string MYGET_API_KEY = Environment.GetEnvironmentVariable(nameof(MYGET_API_KEY)); public static void Main(string[] args) { - var apiKey = Environment.GetEnvironmentVariable("MYGET_API_KEY"); - var srcDirectory = new DirectoryInfo("./src"); + const string clean = nameof(Clean); + const string init = nameof(Init); + const string generateDocumentation = nameof(GenerateDocumentation); + const string build = nameof(Build); + const string runTests = nameof(RunTests); + const string pack = nameof(Pack); + const string publish = nameof(Publish); + const string push = nameof(Push); - Target(Clean, () => - { - if (Directory.Exists(ArtifactsDir)) - { - Directory.Delete(ArtifactsDir, true); - } + var srcDirectory = new DirectoryInfo("./src"); - if (Directory.Exists(PublishDir)) - { - Directory.Delete(PublishDir, true); - } - }); + Target( + clean, + Clean); Target( - Init, - () => - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Run("cmd", "/c yarn", "docs"); - } - else - { - Run("yarn", string.Empty, "docs"); - } - }); + init, + Init); Target( - GenerateDocumentation, - DependsOn(Init), + generateDocumentation, + DependsOn(init), ForEach(SchemaDirectories(srcDirectory)), - schemaDirectory => - RunAsync( - "node", - $"node_modules/@adobe/jsonschema2md/cli.js -n --input {schemaDirectory} --out {schemaDirectory} --schema-out=-", - "docs")); + GenerateDocumentation); Target( - Build, - DependsOn(GenerateDocumentation), - () => Run( - "dotnet", - "build src/SqlStreamStore.HAL.sln -c Release")); + build, + DependsOn(generateDocumentation), + Build); Target( - RunTests, - DependsOn(Build), - () => Run( - "dotnet", - $"test src/SqlStreamStore.HAL.Tests -c Release -r ../../{ArtifactsDir} --verbosity normal --no-build -l trx;LogFileName=SqlStreamStore.HAL.Tests.xml")); + runTests, + DependsOn(build), + RunTests); Target( - Publish, - DependsOn(Build), - () => Run( - "dotnet", - $"publish --configuration=Release --output=../../{PublishDir} --runtime=alpine.3.7-x64 /p:ShowLinkerSizeComparison=true src/SqlStreamStore.HAL.DevServer")); + publish, + DependsOn(build), + Publish); Target( - Pack, - DependsOn(Publish), - () => Run( - "dotnet", - $"pack src/SqlStreamStore.HAL -c Release -o ../../{ArtifactsDir} --no-build")); + pack, + DependsOn(publish), + Pack); Target( - Push, - DependsOn(Pack), - () => - { - var packagesToPush = Directory.GetFiles(ArtifactsDir, "*.nupkg", SearchOption.TopDirectoryOnly); - Console.WriteLine($"Found packages to publish: {string.Join("; ", packagesToPush)}"); - - if (string.IsNullOrWhiteSpace(apiKey)) - { - Console.WriteLine("MyGet API key not available. Packages will not be pushed."); - return; - } - - foreach (var packageToPush in packagesToPush) - { - Run( - "dotnet", - $"nuget push {packageToPush} -s https://www.myget.org/F/sqlstreamstore/api/v3/index.json -k {apiKey}"); - } - }); - - Target("default", DependsOn(Clean, RunTests, Push)); + push, + DependsOn(pack), + Push); + + Target("default", DependsOn(clean, runTests, push)); RunTargets(args.Concat(new[] {"--parallel"})); } + private static readonly Action Init = () => + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Run("cmd", "/c yarn", "docs"); + } + else + { + Run("yarn", string.Empty, "docs"); + } + }; + + private static readonly Action Clean = () => + { + if (Directory.Exists(ArtifactsDir)) + { + Directory.Delete(ArtifactsDir, true); + } + + if (Directory.Exists(PublishDir)) + { + Directory.Delete(PublishDir, true); + } + }; + + private static readonly Func GenerateDocumentation = schemaDirectory => + RunAsync( + "node", + $"node_modules/@adobe/jsonschema2md/cli.js -n --input {schemaDirectory} --out {schemaDirectory} --schema-out=-", + "docs"); + + private static readonly Action Build = () => Run( + "dotnet", + "build src/SqlStreamStore.HAL.sln --configuration Release"); + + private static readonly Action RunTests = () => Run( + "dotnet", + $"test src/SqlStreamStore.HAL.Tests --configuration Release --results-directory ../../{ArtifactsDir} --verbosity normal --no-build -l trx;LogFileName=SqlStreamStore.HAL.Tests.xml"); + + private static readonly Action Publish = () => Run( + "dotnet", + $"publish --configuration=Release --output=../../{PublishDir} --runtime=alpine.3.7-x64 /p:ShowLinkerSizeComparison=true src/SqlStreamStore.HAL.DevServer"); + + private static readonly Action Pack = () => Run( + "dotnet", + $"pack src/SqlStreamStore.HAL --configuration Release --output ../../{ArtifactsDir} --no-build"); + + private static readonly Action Push = () => + { + var packagesToPush = Directory.GetFiles(ArtifactsDir, "*.nupkg", SearchOption.TopDirectoryOnly); + Console.WriteLine($"Found packages to publish: {string.Join("; ", packagesToPush)}"); + + if (string.IsNullOrWhiteSpace(MYGET_API_KEY)) + { + Console.WriteLine("MyGet API key not available. Packages will not be pushed."); + return; + } + + foreach (var packageToPush in packagesToPush) + { + Run( + "dotnet", + $"nuget push {packageToPush} -s https://www.myget.org/F/sqlstreamstore/api/v3/index.json -k {MYGET_API_KEY}"); + } + }; + private static string[] SchemaDirectories(DirectoryInfo srcDirectory) => srcDirectory.GetFiles("*.schema.json", SearchOption.AllDirectories) .Select(schemaFile => schemaFile.DirectoryName) From 3dd06945bec7b929bd9d0ef05df5d6f7eea20910 Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Fri, 23 Nov 2018 17:03:40 +0100 Subject: [PATCH 02/10] use different server for container - uses sql-stream-store/browser container to get the compiled static assets - serve them as embedded content --- .dockerignore | 2 + Dockerfile | 4 +- build/Program.cs | 22 ++- build/build.csproj | 1 + .../ApplicationServerStartup.cs | 67 ++++++++ .../SqlStreamStoreBrowserMiddleware.cs | 57 +++++++ .../Program.cs | 75 +++++++++ ...qlStreamStore.HAL.ApplicationServer.csproj | 32 ++++ .../SqlStreamStoreFactory.cs | 154 ++++++++++++++++++ .../SqlStreamStoreHalConfiguration.cs | 66 ++++++++ .../WebHostBuilderExtensions.cs | 12 ++ .../SqlStreamStore.HAL.DevServer.csproj | 3 - src/SqlStreamStore.HAL.sln | 6 + 13 files changed, 485 insertions(+), 16 deletions(-) create mode 100644 src/SqlStreamStore.HAL.ApplicationServer/ApplicationServerStartup.cs create mode 100644 src/SqlStreamStore.HAL.ApplicationServer/Browser/SqlStreamStoreBrowserMiddleware.cs create mode 100644 src/SqlStreamStore.HAL.ApplicationServer/Program.cs create mode 100644 src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStore.HAL.ApplicationServer.csproj create mode 100644 src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStoreFactory.cs create mode 100644 src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStoreHalConfiguration.cs create mode 100644 src/SqlStreamStore.HAL.ApplicationServer/WebHostBuilderExtensions.cs diff --git a/.dockerignore b/.dockerignore index fb579c7..9f6c88a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,3 +8,5 @@ .vs/ .vscode/ artifacts/ +build.sh +build.cmd \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 36b9f63..4dcc67f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,8 @@ RUN dotnet restore COPY ./build . +COPY --from=sql-stream-store/browser:latest /var/www /app/src/SqlStreamStore.HAL.ApplicationServer/Browser/build + WORKDIR /app RUN MYGET_API_KEY=$MYGET_API_KEY \ @@ -44,4 +46,4 @@ FROM microsoft/dotnet:2.1.6-runtime-deps-alpine3.7 AS runtime WORKDIR /app COPY --from=build /app/publish ./ -ENTRYPOINT ["/app/SqlStreamStore.HAL.DevServer"] +ENTRYPOINT ["/app/SqlStreamStore.HAL.ApplicationServer"] diff --git a/build/Program.cs b/build/Program.cs index 58836e5..dba664b 100644 --- a/build/Program.cs +++ b/build/Program.cs @@ -70,17 +70,7 @@ public static void Main(string[] args) RunTargets(args.Concat(new[] {"--parallel"})); } - private static readonly Action Init = () => - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Run("cmd", "/c yarn", "docs"); - } - else - { - Run("yarn", string.Empty, "docs"); - } - }; + private static readonly Action Init = () => Yarn("./docs"); private static readonly Action Clean = () => { @@ -111,7 +101,7 @@ public static void Main(string[] args) private static readonly Action Publish = () => Run( "dotnet", - $"publish --configuration=Release --output=../../{PublishDir} --runtime=alpine.3.7-x64 /p:ShowLinkerSizeComparison=true src/SqlStreamStore.HAL.DevServer"); + $"publish --configuration=Release --output=../../{PublishDir} --runtime=alpine.3.7-x64 /p:ShowLinkerSizeComparison=true src/SqlStreamStore.HAL.ApplicationServer"); private static readonly Action Pack = () => Run( "dotnet", @@ -136,6 +126,14 @@ public static void Main(string[] args) } }; + private static void Yarn(string workingDirectory, string args = default) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Run("cmd", "/c yarn", workingDirectory); + else + Run("yarn", args, workingDirectory); + } + private static string[] SchemaDirectories(DirectoryInfo srcDirectory) => srcDirectory.GetFiles("*.schema.json", SearchOption.AllDirectories) .Select(schemaFile => schemaFile.DirectoryName) diff --git a/build/build.csproj b/build/build.csproj index c0a0dfb..5e76118 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -3,6 +3,7 @@ Exe netcoreapp2.1 + latest diff --git a/src/SqlStreamStore.HAL.ApplicationServer/ApplicationServerStartup.cs b/src/SqlStreamStore.HAL.ApplicationServer/ApplicationServerStartup.cs new file mode 100644 index 0000000..3b90503 --- /dev/null +++ b/src/SqlStreamStore.HAL.ApplicationServer/ApplicationServerStartup.cs @@ -0,0 +1,67 @@ +namespace SqlStreamStore.HAL.ApplicationServer +{ + using System; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Serilog; + using SqlStreamStore.HAL.ApplicationServer.Browser; + using MidFunc = System.Func< + Microsoft.AspNetCore.Http.HttpContext, + System.Func, + System.Threading.Tasks.Task + >; + + internal class ApplicationServerStartup : IStartup + { + private readonly IStreamStore _streamStore; + private readonly SqlStreamStoreMiddlewareOptions _options; + + public ApplicationServerStartup( + IStreamStore streamStore, + SqlStreamStoreMiddlewareOptions options) + { + _streamStore = streamStore; + _options = options; + } + + public IServiceProvider ConfigureServices(IServiceCollection services) => services + .AddResponseCompression(options => options.MimeTypes = new[] { "application/hal+json" }) + .BuildServiceProvider(); + + public void Configure(IApplicationBuilder app) => app + .UseResponseCompression() + .Use(VaryAccept) + .Use(CatchAndDisplayErrors) + .UseSqlStreamStoreBrowser() + .UseSqlStreamStoreHal(_streamStore, _options); + + private static MidFunc CatchAndDisplayErrors => async (context, next) => + { + try + { + await next(); + } + catch(Exception ex) + { + Log.Warning(ex, "Error during request."); + } + }; + + private static MidFunc VaryAccept => (context, next) => + { + Task Vary() + { + context.Response.Headers.AppendCommaSeparatedValues("Vary", "Accept"); + + return Task.CompletedTask; + } + + context.Response.OnStarting(Vary); + + return next(); + }; + } +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL.ApplicationServer/Browser/SqlStreamStoreBrowserMiddleware.cs b/src/SqlStreamStore.HAL.ApplicationServer/Browser/SqlStreamStoreBrowserMiddleware.cs new file mode 100644 index 0000000..68b17dd --- /dev/null +++ b/src/SqlStreamStore.HAL.ApplicationServer/Browser/SqlStreamStoreBrowserMiddleware.cs @@ -0,0 +1,57 @@ +namespace SqlStreamStore.HAL.ApplicationServer.Browser +{ + using System; + using System.Linq; + using System.Net.Http.Headers; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.FileProviders; + using Serilog; + using MidFunc = System.Func< + Microsoft.AspNetCore.Http.HttpContext, + System.Func, + System.Threading.Tasks.Task + >; + + internal static class SqlStreamStoreBrowserMiddleware + { + public static IApplicationBuilder UseSqlStreamStoreBrowser( + this IApplicationBuilder builder) + { + var sqlStreamStoreBrowserFileProvider = new EmbeddedFileProvider( + typeof(SqlStreamStoreBrowserMiddleware).Assembly, + typeof(SqlStreamStoreBrowserMiddleware).Namespace); + + var staticFiles = typeof(SqlStreamStoreBrowserMiddleware).Assembly.GetManifestResourceNames() + .Where(name => name.StartsWith(typeof(SqlStreamStoreBrowserMiddleware).Namespace)) + .Select(name => name.Remove(0, typeof(SqlStreamStoreBrowserMiddleware).Namespace.Length + 1) + .Replace('.', '/')); + + Log.Information( + $"The following files will be served as static content: {string.Join(", ", staticFiles)}"); + + return builder.Use(IndexPage).UseStaticFiles(new StaticFileOptions + { + FileProvider = sqlStreamStoreBrowserFileProvider + }); + + Task IndexPage(HttpContext context, Func next) + { + if(GetAcceptHeaders(context.Request).Contains("text/html")) + { + context.Request.Path = new PathString("/index.html"); + } + + return next(); + } + } + + private static string[] GetAcceptHeaders(HttpRequest contextRequest) + => Array.ConvertAll( + contextRequest.Headers.GetCommaSeparatedValues("Accept"), + value => MediaTypeWithQualityHeaderValue.TryParse(value, out var header) + ? header.MediaType + : null); + } +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL.ApplicationServer/Program.cs b/src/SqlStreamStore.HAL.ApplicationServer/Program.cs new file mode 100644 index 0000000..d356988 --- /dev/null +++ b/src/SqlStreamStore.HAL.ApplicationServer/Program.cs @@ -0,0 +1,75 @@ +namespace SqlStreamStore.HAL.ApplicationServer +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Hosting; + using Serilog; + + internal class Program : IDisposable + { + private readonly CancellationTokenSource _cts; + private readonly SqlStreamStoreHalConfiguration _configuration; + private readonly SqlStreamStoreFactory _factory; + + public static async Task Main(string[] args) + { + using(var program = new Program(args)) + { + return await program.Run(); + } + } + + private Program(string[] args) + { + _configuration = new SqlStreamStoreHalConfiguration(Environment.GetEnvironmentVariables(), args); + + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Is(_configuration.LogLevel) + .Enrich.FromLogContext() + .WriteTo.Console() + .CreateLogger(); + + _cts = new CancellationTokenSource(); + _factory = new SqlStreamStoreFactory(_configuration); + } + + private async Task Run() + { + try + { + using(var streamStore = await _factory.Create(_cts.Token)) + using(var host = new WebHostBuilder() + .UseKestrel() + .UseStartup(new ApplicationServerStartup(streamStore, + new SqlStreamStoreMiddlewareOptions + { + UseCanonicalUrls = _configuration.UseCanonicalUris + })) + .UseSerilog() + .Build()) + { + await Task.WhenAll( + host.RunAsync(_cts.Token), + host.WaitForShutdownAsync(_cts.Token)); + + return 0; + } + } + catch(Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly."); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + public void Dispose() + { + _cts?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStore.HAL.ApplicationServer.csproj b/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStore.HAL.ApplicationServer.csproj new file mode 100644 index 0000000..48774d9 --- /dev/null +++ b/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStore.HAL.ApplicationServer.csproj @@ -0,0 +1,32 @@ + + + Exe + netcoreapp2.1 + alpine.3.7-x64 + true + latest + false + + + + + + + + + + + + + + + + + + + + + Browser\%(RecursiveDir)%(Filename)%(Extension) + + + diff --git a/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStoreFactory.cs b/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStoreFactory.cs new file mode 100644 index 0000000..0e62724 --- /dev/null +++ b/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStoreFactory.cs @@ -0,0 +1,154 @@ +namespace SqlStreamStore.HAL.ApplicationServer +{ + using System; + using System.Collections.Generic; + using System.Data.SqlClient; + using System.Threading; + using System.Threading.Tasks; + using Npgsql; + using Serilog; + using SqlStreamStore.Infrastructure; + + internal class SqlStreamStoreFactory + { + private readonly SqlStreamStoreHalConfiguration _configuration; + + private delegate Task CreateStreamStore( + string connectionString, + string schema, + CancellationToken cancellationToken); + + private const string postgres = nameof(postgres); + private const string mssql = nameof(mssql); + private const string inmemory = nameof(inmemory); + + private static readonly IDictionary s_factories + = new Dictionary + { + [inmemory] = CreateInMemoryStreamStore, + [postgres] = CreatePostgresStreamStore, + [mssql] = CreateMssqlStreamStore + }; + + public SqlStreamStoreFactory(SqlStreamStoreHalConfiguration configuration) + { + if(configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + _configuration = configuration; + } + + public Task Create(CancellationToken cancellationToken = default) + { + var provider = _configuration.Provider?.ToLowerInvariant() + ?? inmemory; + + Log.Information($"Creating stream store for provider '{provider}'"); + + if(!s_factories.TryGetValue(provider, out var factory)) + { + throw new InvalidOperationException($"No provider factory for provider '{provider}' found."); + } + + var connectionString = _configuration.ConnectionString; + var schema = _configuration.Schema; + + return factory(connectionString, schema, cancellationToken); + } + + private static Task CreateInMemoryStreamStore( + string connectionString, + string schema, + CancellationToken cancellationToken) + => Task.FromResult(new InMemoryStreamStore()); + + private static async Task CreateMssqlStreamStore( + string connectionString, + string schema, + CancellationToken cancellationToken) + { + var connectionStringBuilder = new SqlConnectionStringBuilder(connectionString); + using(var connection = new SqlConnection(new SqlConnectionStringBuilder(connectionString) + { + InitialCatalog = "master" + }.ConnectionString)) + { + await connection.OpenAsync(cancellationToken).NotOnCapturedContext(); + + using(var command = new SqlCommand( + $@" +IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'{connectionStringBuilder.InitialCatalog}') +BEGIN + CREATE DATABASE [{connectionStringBuilder.InitialCatalog}] +END; +", + connection)) + { + await command.ExecuteNonQueryAsync(cancellationToken).NotOnCapturedContext(); + } + } + + var settings = new MsSqlStreamStoreV3Settings(connectionString); + + if(schema != null) + { + settings.Schema = schema; + } + + var streamStore = new MsSqlStreamStoreV3(settings); + + await streamStore.CreateSchema(cancellationToken); + + return streamStore; + } + + private static async Task CreatePostgresStreamStore( + string connectionString, + string schema, + CancellationToken cancellationToken) + { + var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString); + + using(var connection = new NpgsqlConnection(new NpgsqlConnectionStringBuilder(connectionString) + { + Database = null + }.ConnectionString)) + { + bool exists; + await connection.OpenAsync(cancellationToken).NotOnCapturedContext(); + + using(var command = new NpgsqlCommand( + $"SELECT 1 FROM pg_database WHERE datname = '{connectionStringBuilder.Database}'", + connection)) + { + exists = await command.ExecuteScalarAsync(cancellationToken).NotOnCapturedContext() + != null; + } + + if(!exists) + { + using(var command = new NpgsqlCommand( + $"CREATE DATABASE {connectionStringBuilder.Database}", + connection)) + { + await command.ExecuteNonQueryAsync(cancellationToken).NotOnCapturedContext(); + } + } + + var settings = new PostgresStreamStoreSettings(connectionString); + + if(schema != null) + { + settings.Schema = schema; + } + + var streamStore = new PostgresStreamStore(settings); + + await streamStore.CreateSchema(cancellationToken); + + return streamStore; + } + } + } +} diff --git a/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStoreHalConfiguration.cs b/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStoreHalConfiguration.cs new file mode 100644 index 0000000..80b1101 --- /dev/null +++ b/src/SqlStreamStore.HAL.ApplicationServer/SqlStreamStoreHalConfiguration.cs @@ -0,0 +1,66 @@ +namespace SqlStreamStore.HAL.ApplicationServer +{ + using System; + using System.Collections; + using System.Linq; + using Microsoft.Extensions.Configuration; + using Serilog.Events; + + internal class SqlStreamStoreHalConfiguration + { + private readonly IConfigurationRoot _configuration; + + public bool UseCanonicalUris => _configuration.GetValue("use-canonical-uris"); + public LogEventLevel LogLevel => _configuration.GetValue("log-level", LogEventLevel.Information); + public string ConnectionString => _configuration.GetValue("connection-string"); + public string Schema => _configuration.GetValue("schema"); + public string Provider => _configuration.GetValue("provider"); + + public SqlStreamStoreHalConfiguration(IDictionary environment, string[] args) + { + if(environment == null) + throw new ArgumentNullException(nameof(environment)); + if(args == null) + throw new ArgumentNullException(nameof(args)); + _configuration = new ConfigurationBuilder() + .AddCommandLine(args) + .Add(new UpperCasedEnvironmentVariablesConfigurationSource(environment)) + .Build(); + } + + private class UpperCasedEnvironmentVariablesConfigurationSource : IConfigurationSource + { + private readonly IDictionary _environment; + public string Prefix { get; set; } = "SQLSTREAMSTORE"; + + public UpperCasedEnvironmentVariablesConfigurationSource(IDictionary environment) + { + _environment = environment; + } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + => new UpperCasedEnvironmentVariablesConfigurationProvider(Prefix, _environment); + } + + private class UpperCasedEnvironmentVariablesConfigurationProvider : ConfigurationProvider + { + private readonly IDictionary _environment; + private readonly string _prefix; + + public UpperCasedEnvironmentVariablesConfigurationProvider(string prefix, IDictionary environment) + { + _environment = environment; + _prefix = $"{prefix}_"; + } + + public override void Load() + { + Data = (from entry in _environment.OfType() + let key = (string) entry.Key + where key.StartsWith(_prefix) + select new { key = key.Remove(0, _prefix.Length).Replace('_', '-').ToLowerInvariant(), value = (string) entry.Value }) + .ToDictionary(x => x.key, x => x.value); + } + } + } +} diff --git a/src/SqlStreamStore.HAL.ApplicationServer/WebHostBuilderExtensions.cs b/src/SqlStreamStore.HAL.ApplicationServer/WebHostBuilderExtensions.cs new file mode 100644 index 0000000..2130a49 --- /dev/null +++ b/src/SqlStreamStore.HAL.ApplicationServer/WebHostBuilderExtensions.cs @@ -0,0 +1,12 @@ +namespace SqlStreamStore.HAL.ApplicationServer +{ + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.DependencyInjection; + + internal static class WebHostBuilderExtensions + { + public static IWebHostBuilder UseStartup(this IWebHostBuilder builder, IStartup startup) + => builder + .ConfigureServices(services => services.AddSingleton(startup)); + } +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL.DevServer/SqlStreamStore.HAL.DevServer.csproj b/src/SqlStreamStore.HAL.DevServer/SqlStreamStore.HAL.DevServer.csproj index f230359..11b5669 100644 --- a/src/SqlStreamStore.HAL.DevServer/SqlStreamStore.HAL.DevServer.csproj +++ b/src/SqlStreamStore.HAL.DevServer/SqlStreamStore.HAL.DevServer.csproj @@ -2,14 +2,11 @@ Exe netcoreapp2.1 - alpine.3.7-x64 true latest false - - diff --git a/src/SqlStreamStore.HAL.sln b/src/SqlStreamStore.HAL.sln index 1f8af3a..9acc873 100644 --- a/src/SqlStreamStore.HAL.sln +++ b/src/SqlStreamStore.HAL.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlStreamStore.HAL.DevServe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LittleHalHost.Example", "LittleHalHost.Example\LittleHalHost.Example.csproj", "{41C59355-DADF-4D62-9552-F298967D27A9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlStreamStore.HAL.ApplicationServer", "SqlStreamStore.HAL.ApplicationServer\SqlStreamStore.HAL.ApplicationServer.csproj", "{B4B24F09-CB96-4D2E-A1D2-C50C9557F144}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {41C59355-DADF-4D62-9552-F298967D27A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {41C59355-DADF-4D62-9552-F298967D27A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {41C59355-DADF-4D62-9552-F298967D27A9}.Release|Any CPU.Build.0 = Release|Any CPU + {B4B24F09-CB96-4D2E-A1D2-C50C9557F144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4B24F09-CB96-4D2E-A1D2-C50C9557F144}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4B24F09-CB96-4D2E-A1D2-C50C9557F144}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4B24F09-CB96-4D2E-A1D2-C50C9557F144}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d47ea79a410623b0aca56796803362eb73029b31 Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Sun, 25 Nov 2018 08:17:38 +0100 Subject: [PATCH 03/10] semantically versioning the container --- Dockerfile | 12 +++++++----- build.sh | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4dcc67f..c41b83a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,16 @@ FROM microsoft/dotnet:2.1.500-sdk-alpine3.7 AS build ARG MYGET_API_KEY +WORKDIR /app + +COPY .git ./ + RUN apk add --no-cache \ nodejs \ yarn \ - libcurl + libcurl && \ + dotnet tool install -g minver-cli --version 1.0.0-alpha.15 && \ + /root/.dotnet/tools/minver > .version WORKDIR /app/src @@ -22,10 +28,6 @@ WORKDIR /app/docs COPY ./docs/package.json ./docs/yarn.lock ./ -WORKDIR /app/.git - -COPY ./.git . - WORKDIR /app/build COPY ./build/build.csproj . diff --git a/build.sh b/build.sh index ae1b06c..c06f923 100755 --- a/build.sh +++ b/build.sh @@ -1,15 +1,42 @@ #!/usr/bin/env bash + set -e -DOCKERTAG=${TRAVIS_TAG:-latest} -BUILD_NUMBER=${TRAVIS_BUILD_NUMBER:-0} -COMMIT=${TRAVIS_PULL_REQUEST_SHA:-${TRAVIS_COMMIT:-unknown}} -MINVERBUILDMETADATA="build.${BUILD_NUMBER}.${COMMIT}" +LOCAL_IMAGE="sql-stream-store-server" +LOCAL="${LOCAL_IMAGE}:latest" + +REMOTE_IMAGE="sql-stream-store/server" docker build \ - --build-arg MINVERBUILDMETADATA=$MINVERBUILDMETADATA \ --build-arg MYGET_API_KEY=$MYGET_API_KEY \ - --tag sql-stream-store-server:${DOCKERTAG} \ + --tag ${LOCAL} \ . -docker images --filter=reference="sql-stream-store-server:${DOCKERTAG}" \ No newline at end of file +VERSION=$(docker run --entrypoint=cat ${LOCAL} /app/.version) + +SEMVER_REGEX="^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + +[[ $VERSION =~ $SEMVER_REGEX ]] + +LATEST="${REMOTE_IMAGE}:latest" +MAJOR="${REMOTE_IMAGE}:${BASH_REMATCH[1]}" +MAJOR_MINOR="${REMOTE_IMAGE}:${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" +MAJOR_MINOR_PATCH="${REMOTE_IMAGE}:${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" +MAJOR_MINOR_PATCH_PRE="${REMOTE_IMAGE}:${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}${BASH_REMATCH[4]}" + +if [[ -n $TRAVIS_TAG && -z ${BASH_REMATCH[4]} ]]; then + echo "Detected a tag with no prerelease." + docker tag $LOCAL $LATEST + docker tag $LOCAL $MAJOR_MINOR_PATCH + docker tag $LOCAL $MAJOR_MINOR + if [[ ${BASH_REMATCH[1]} != "0" ]]; then + docker tag $LOCAL $MAJOR + else + echo "Detected unstable version." + fi +else + echo "Detected a prerelease." + docker tag $LOCAL $MAJOR_MINOR_PATCH_PRE +fi + +docker images --filter=reference="${REMOTE_IMAGE}" From a95f133886b1e22c97ec0778fcc090bb01aa6f6b Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Mon, 26 Nov 2018 08:29:55 +0100 Subject: [PATCH 04/10] Don't auto tag latest it's possible to have 1.2.3 be released after 2.0.0 but 2.0.0 should be latest --- build.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.sh b/build.sh index c06f923..79148af 100755 --- a/build.sh +++ b/build.sh @@ -18,7 +18,6 @@ SEMVER_REGEX="^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z [[ $VERSION =~ $SEMVER_REGEX ]] -LATEST="${REMOTE_IMAGE}:latest" MAJOR="${REMOTE_IMAGE}:${BASH_REMATCH[1]}" MAJOR_MINOR="${REMOTE_IMAGE}:${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" MAJOR_MINOR_PATCH="${REMOTE_IMAGE}:${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" @@ -26,7 +25,6 @@ MAJOR_MINOR_PATCH_PRE="${REMOTE_IMAGE}:${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${B if [[ -n $TRAVIS_TAG && -z ${BASH_REMATCH[4]} ]]; then echo "Detected a tag with no prerelease." - docker tag $LOCAL $LATEST docker tag $LOCAL $MAJOR_MINOR_PATCH docker tag $LOCAL $MAJOR_MINOR if [[ ${BASH_REMATCH[1]} != "0" ]]; then From 9b754a106872627ee2535b86f6cd6794a616950e Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Mon, 26 Nov 2018 09:26:25 +0100 Subject: [PATCH 05/10] version file is not in the publish folder --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index c41b83a..a5f6282 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,6 +46,7 @@ RUN MYGET_API_KEY=$MYGET_API_KEY \ FROM microsoft/dotnet:2.1.6-runtime-deps-alpine3.7 AS runtime WORKDIR /app +COPY --from=build /app/.version ./ COPY --from=build /app/publish ./ ENTRYPOINT ["/app/SqlStreamStore.HAL.ApplicationServer"] From 130b809bc6ec0fbc0f078e8d09a73565094d5a92 Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Mon, 26 Nov 2018 15:04:18 +0100 Subject: [PATCH 06/10] fixed logging --- .../Browser/SqlStreamStoreBrowserMiddleware.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/SqlStreamStore.HAL.ApplicationServer/Browser/SqlStreamStoreBrowserMiddleware.cs b/src/SqlStreamStore.HAL.ApplicationServer/Browser/SqlStreamStoreBrowserMiddleware.cs index 68b17dd..5376a26 100644 --- a/src/SqlStreamStore.HAL.ApplicationServer/Browser/SqlStreamStoreBrowserMiddleware.cs +++ b/src/SqlStreamStore.HAL.ApplicationServer/Browser/SqlStreamStoreBrowserMiddleware.cs @@ -24,12 +24,10 @@ public static IApplicationBuilder UseSqlStreamStoreBrowser( typeof(SqlStreamStoreBrowserMiddleware).Namespace); var staticFiles = typeof(SqlStreamStoreBrowserMiddleware).Assembly.GetManifestResourceNames() - .Where(name => name.StartsWith(typeof(SqlStreamStoreBrowserMiddleware).Namespace)) - .Select(name => name.Remove(0, typeof(SqlStreamStoreBrowserMiddleware).Namespace.Length + 1) - .Replace('.', '/')); + .Where(name => name.StartsWith(typeof(SqlStreamStoreBrowserMiddleware).Namespace)); - Log.Information( - $"The following files will be served as static content: {string.Join(", ", staticFiles)}"); + Log.Debug( + $"The following embedded resources were found and will be served as static content: {string.Join(", ", staticFiles)}"); return builder.Use(IndexPage).UseStaticFiles(new StaticFileOptions { From f5e5e8bcf938edc4eafa827c526b722312cc4da5 Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Thu, 29 Nov 2018 12:03:28 +0100 Subject: [PATCH 07/10] using correct docker image --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 79148af..378c94f 100755 --- a/build.sh +++ b/build.sh @@ -5,7 +5,7 @@ set -e LOCAL_IMAGE="sql-stream-store-server" LOCAL="${LOCAL_IMAGE}:latest" -REMOTE_IMAGE="sql-stream-store/server" +REMOTE_IMAGE="sqlstreamstore/hal" docker build \ --build-arg MYGET_API_KEY=$MYGET_API_KEY \ From f74187eb23c5eb0ad6e8be7b7307aae9b4ad968f Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Sun, 2 Dec 2018 10:40:17 +0100 Subject: [PATCH 08/10] using 0.9 version of the browser container --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a5f6282..b42aba3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ RUN dotnet restore COPY ./build . -COPY --from=sql-stream-store/browser:latest /var/www /app/src/SqlStreamStore.HAL.ApplicationServer/Browser/build +COPY --from=sqlstreamstore/browser:0.9 /var/www /app/src/SqlStreamStore.HAL.ApplicationServer/Browser/build WORKDIR /app From 57d47f8ecab5c24f3f982e0b6978e1b8310b1fc6 Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Sun, 2 Dec 2018 10:51:06 +0100 Subject: [PATCH 09/10] pushing to docker --- build.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.sh b/build.sh index 378c94f..6348f58 100755 --- a/build.sh +++ b/build.sh @@ -37,4 +37,9 @@ else docker tag $LOCAL $MAJOR_MINOR_PATCH_PRE fi +if [[ -n $DOCKER_USER ]]; then + echo "${DOCKER_PASS}" | docker login --username "${DOCKER_USER}" --password-stdin + docker push $REMOTE_IMAGE +fi + docker images --filter=reference="${REMOTE_IMAGE}" From 3bb094d24117c8e39ec91c66a5631fa11610bc54 Mon Sep 17 00:00:00 2001 From: thefringeninja Date: Sun, 2 Dec 2018 10:58:32 +0100 Subject: [PATCH 10/10] docker credentials --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index edbb6fc..4f6bd3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: minimal sudo: required dist: trusty -script: ./build.sh +script: "./build.sh" env: global: - secure: pnK+4X/2WLLMqEv0A2GAXHYkjjRKT+HeIOkSWLYvQc/2H8lPjg0JmQvq7MQIZJxetGv+gxSJtX2cbG55j6mj+inlHHWm87wHPh8zljJztRpH9Fz0IR5GXADuUjCRuvoHq+PJI5MZlcFPYbcaAKVOsyYlejy5Ok5wlYD2WUMMw4NH+iFmffbAuycVynZrzeEGzfx33nocUObamrMOru8UTcHEmR041mwOwjjkzzQmNSLaSt/sQvSrnR8jte3zfrCUT5H7uczs0lkO3OzhJAwqA7vULyRH9iugkauv0HzAorKXOMLKey6E/GZElHviU8E1NnbAOyoBKcITe4EDI1HSbWNQ8C7nFP6w8Rv/O1lRXWL8SHucNXbmETgJ8zlrVWlRMBmnjnjbr0uF7cK4lIaJG3B/ZsY6pMn/93pPLTLrlsl7ZCjxuplU5pfNq/ORnZnAhlKe5W2uxvNdK1C6soFaGvcvyMlcAcjx8Jhjq3Yh8sHknAl70ga/AhvjAzWaTKe5zXTxfXcRXQp5mF9k707qTDkDIeGwXxY92QeiRmFfOuH56UI4CsLgpPgk3yyaHTYZsVuJRHRSqHcNo6ivgF/vYRzSXreEWfd89d8E750SMf90ON8GiCu0cfgBbUUFtsZ8AJdwmp9F7JB23sEB9Gj1OTLgRT247hOmP0b4tinZz5M= + - secure: pnK+4X/2WLLMqEv0A2GAXHYkjjRKT+HeIOkSWLYvQc/2H8lPjg0JmQvq7MQIZJxetGv+gxSJtX2cbG55j6mj+inlHHWm87wHPh8zljJztRpH9Fz0IR5GXADuUjCRuvoHq+PJI5MZlcFPYbcaAKVOsyYlejy5Ok5wlYD2WUMMw4NH+iFmffbAuycVynZrzeEGzfx33nocUObamrMOru8UTcHEmR041mwOwjjkzzQmNSLaSt/sQvSrnR8jte3zfrCUT5H7uczs0lkO3OzhJAwqA7vULyRH9iugkauv0HzAorKXOMLKey6E/GZElHviU8E1NnbAOyoBKcITe4EDI1HSbWNQ8C7nFP6w8Rv/O1lRXWL8SHucNXbmETgJ8zlrVWlRMBmnjnjbr0uF7cK4lIaJG3B/ZsY6pMn/93pPLTLrlsl7ZCjxuplU5pfNq/ORnZnAhlKe5W2uxvNdK1C6soFaGvcvyMlcAcjx8Jhjq3Yh8sHknAl70ga/AhvjAzWaTKe5zXTxfXcRXQp5mF9k707qTDkDIeGwXxY92QeiRmFfOuH56UI4CsLgpPgk3yyaHTYZsVuJRHRSqHcNo6ivgF/vYRzSXreEWfd89d8E750SMf90ON8GiCu0cfgBbUUFtsZ8AJdwmp9F7JB23sEB9Gj1OTLgRT247hOmP0b4tinZz5M= + - secure: fKxyki9JzY95G17hNHSy4dgDmH+zXIk50BR6honAz7LmsA9jGv2gg+l1AIUERH3eckdJwxze4K0e5hX88Xfn4j5nN8TnEw7Wwn7c1vjR9FxvRr5cjaJC0SVsOTZj4VC1R9Sqjzrkqv3jJDtDdz9B8aZMdEzJ+BkHwUMFxmuko2p2qabKnAonaGk5VDSTtDyvDMa0aFjKDYuB6MrnrxhfCzLk1ciyGL61SNfz0/u7Hj3xC6v4U0f0BQ9n65l5dIVn+mpuCdQ7GO0HGj0ySo7ffif3qqXXTx7ZWIl3wvf5RCp0PouA8q2dRHYYyGqhhCwuRNU7dfhS/eFtUtcITclZ/DJE86NBQGJ9tpUUhL7BJ3sNJZzbajh1F28paJgi595SKpScp4VGx3iBuDSKH4eb3cRj5TJqCUoscue8/uIZGgR4PGkGF4lbad3b7xexIk1YY/2Rwun1J0N7tqS0rej3ZRZBtipMF64NrVnt9dZzgsIlssZ0W2NvG0vcLX6DUdeZaR0diz8bUl1DXrMFxAUQ/QYxilvaESmCslRElzlb+5eaqQTG5lSP5l6f3ZcKAhvcUGggu9t3vjzpEq3zFa5dorCyK+es1ASNlwZtZr8in9oYkvam9aiDkZD8cEc+9u+qxvnX9w3MsA0P047YCMQGgwRl5dpizoySC3wr52voK7I= + - secure: HvSSWjsLR6qFi9/iHMKKoVUT4BDN7h4QjKHLvcrznwpfojrA1+nIlIwgcaIkxi5WClsoPmwW8YInPlyIyV+37+SaNK4sxB2puzwaC3iHBi6Ql38q+olsLdCE8u+DcfEcnzQo8pinNoyBLRjBgHjTAbrwIkLETNOZchss4A4UVhvPSznuNz5imlZxp4LqZ2pW/YcaLeCMQzuJHt5iRM6O4CMu7O4kMP/SPQXIXcqqYWI+o4um1JFO5juXBXtrhcTkOHfvGjqZghwma1F3v7zLHtqrbGBxaTLGNj0EIWPQhQEE5wDpvAqLEZcySl0ZjsPQUJd2Uxi9b8mWM1x6WaZTu9uqIy5t9lkCwUkEAwc/LUXZxwL0oi8HncgJvu7Q5KwVn375sJ0CER0Vn1FO8jphnMbpfkqUmbZmkwoIAUENNNYyGEp6zoNnRvaFQTjEW4bL2huPS0g3BgC2xyh8d5ZsPykIgQXlM8EEA2BZkd+ep2rKjOPT94rs5V5IPuVi9FYzFIsc2kpEkKZO/38j6pEAdCVIZp1m0+KRlLNQ9FeaI+xtil95V5ALIEXpc44o5OuUSoxt5ZirYaGSJtUtxo8E+HqpNL5WrTUeE8+aZos6Tu7jF63U2ehwg5xR6EubvrBjWvSHsQKcwDfdeHK2m15S/mVlahpwyyoCASAJtV7mRJI= git: - depth: false \ No newline at end of file + depth: false