Williamhe/add oryx server command (#1208)

* update

* update error handling for oryx buildendpoint

* update

* update

* update error handling

* update response header with status check urls

* update

* update

* Update for async

* Update build server startup

* Update build server controller

* Add health check

* Add build server builder

* Remove unnecessary code

* Add Microsoft copyright header

* Add build server documentation

* Remove extra newlines

* Update to store JSON flat data store in /store directory

* Add buildservice unit tests

* Remove unnecessary code

* Update build server doc images

* Update solution

* Update solution

Co-authored-by: Arjun Roy Chaudhuri <arroyc@microsoft.com>
This commit is contained in:
william-msft 2022-02-08 15:52:23 -08:00 коммит произвёл GitHub
Родитель a62ad97a95
Коммит b5fbbd4c21
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
31 изменённых файлов: 897 добавлений и 8 удалений

Просмотреть файл

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.10.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\BuildServer\BuildServer.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,141 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
using Microsoft.Oryx.BuildServer.Repositories;
using Microsoft.Oryx.BuildServer.Services;
using Microsoft.Oryx.BuildServer.Services.ArtifactBuilders;
using Moq;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Oryx.BuildServer.Tests
{
public class BuildServiceTests
{
[Fact]
public async Task Test_CorrectBuildStatus_OnStartBuild()
{
// Arrange
var build = TestBuild();
var mockedBuildRepository = new Mock<IRepository>();
var mockedArtifactBuilderFactory = new Mock<IArtifactBuilderFactory>();
var mockedArtifactBuilder = new Mock<IArtifactBuilder>();
var mockedBuildRunner = new Mock<IBuildRunner>();
mockedArtifactBuilder.Setup(x => x.Build(build)).Returns(true);
mockedArtifactBuilderFactory.Setup(x => x.CreateArtifactBuilder(build)).Returns(mockedArtifactBuilder.Object);
mockedBuildRunner.Setup(x => x.RunInBackground(mockedArtifactBuilder.Object, build, null, null));
var testBuildService = TestBuildService(
mockedBuildRepository.Object, mockedArtifactBuilderFactory.Object, mockedBuildRunner.Object);
// Act
var buildTest = await testBuildService.StartBuild(build);
// Assert
Assert.NotNull(buildTest);
Assert.Equal("IN_PROGRESS", buildTest.Status);
}
[Fact]
public async Task Test_GetById_CorrectBuildStatus()
{
// Arrange
var build = TestBuild();
var mockedBuildRepository = new Mock<IRepository>();
mockedBuildRepository.Setup(x => x.GetById(build.Id)).Returns(build);
var mockedArtifactBuilderFactory = new Mock<IArtifactBuilderFactory>();
var mockedBuildRunner = new Mock<IBuildRunner>();
var testBuildService = TestBuildService(
mockedBuildRepository.Object, mockedArtifactBuilderFactory.Object, mockedBuildRunner.Object);
// Act
var buildTest = await testBuildService.GetBuild(build.Id);
// Assert
Assert.NotNull(buildTest);
Assert.Equal("testId", buildTest.Id);
Assert.Equal("testStatus", buildTest.Status);
}
[Fact]
public async Task Test_MarkedCancelled_GetsCorrectBuildStatus()
{
// Arrange
var build = TestBuild();
var mockedBuildRepository = new Mock<IRepository>();
var mockedArtifactBuilderFactory = new Mock<IArtifactBuilderFactory>();
var mockedBuildRunner = new Mock<IBuildRunner>();
var testBuildService = TestBuildService(
mockedBuildRepository.Object, mockedArtifactBuilderFactory.Object, mockedBuildRunner.Object);
// Act
var buildTest = await testBuildService.MarkCancelled(build);
// Assert
Assert.NotNull(buildTest);
Assert.Equal("CANCELLED", buildTest.Status);
}
[Fact]
public async Task Test_MarkedCompleted_GetsCorrectBuildStatus()
{
// Arrange
var build = TestBuild();
var mockedBuildRepository = new Mock<IRepository>();
var mockedArtifactBuilderFactory = new Mock<IArtifactBuilderFactory>();
var mockedBuildRunner = new Mock<IBuildRunner>();
var testBuildService = TestBuildService(
mockedBuildRepository.Object, mockedArtifactBuilderFactory.Object, mockedBuildRunner.Object);
// Act
var buildTest = await testBuildService.MarkCompleted(build);
// Assert
Assert.NotNull(buildTest);
Assert.Equal("COMPLETED", buildTest.Status);
}
[Fact]
public async Task Test_MarkedFailed_GetsCorrectBuildStatus()
{
// Arrange
var build = TestBuild();
var mockedBuildRepository = new Mock<IRepository>();
var mockedArtifactBuilderFactory = new Mock<IArtifactBuilderFactory>();
var mockedBuildRunner = new Mock<IBuildRunner>();
var testBuildService = TestBuildService(
mockedBuildRepository.Object, mockedArtifactBuilderFactory.Object, mockedBuildRunner.Object);
// Act
var buildTest = await testBuildService.MarkFailed(build);
// Assert
Assert.NotNull(buildTest);
Assert.Equal("FAILED", buildTest.Status);
}
private static Build TestBuild()
{
var build = new Build();
build.Id = "testId";
build.Status = "testStatus";
build.Platform = "testPlatform";
build.Version = "1.2.3";
build.SourcePath = "/test/sourcePath";
build.OutputPath = "/test/outputPath";
build.LogPath = "/test/logPath";
return build;
}
private static BuildService TestBuildService(
IRepository testIRepository,
IArtifactBuilderFactory testIArtifactBuilderFactory,
IBuildRunner testIBuildRunner)
{
return new BuildService(testIRepository, testIArtifactBuilderFactory, testIBuildRunner);
}
}
}

Просмотреть файл

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30104.148
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7515BF2B-C485-43A0-AB2F-DC761B6AB151}"
EndProject
@ -96,7 +96,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "src\Oryx.Common\C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildScriptGenerator.Common.Tests", "tests\BuildScriptGenerator.Common.Tests\BuildScriptGenerator.Common.Tests.csproj", "{3078BCDB-FC35-4826-82A1-9CE094446DE8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdater", "build\tools\AutoUpdater\AutoUpdater.csproj", "{6C4DBA89-3D17-451C-9D89-71A70410D835}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildServer", "src\BuildServer\BuildServer.csproj", "{F13BEF5A-5426-4F77-879F-A7F812F1E336}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildServer.Tests", "BuildServer.Tests\BuildServer.Tests.csproj", "{5BD3E03A-3086-4CF6-950B-215C1E76B7F5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -164,10 +166,14 @@ Global
{3078BCDB-FC35-4826-82A1-9CE094446DE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3078BCDB-FC35-4826-82A1-9CE094446DE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3078BCDB-FC35-4826-82A1-9CE094446DE8}.Release|Any CPU.Build.0 = Release|Any CPU
{6C4DBA89-3D17-451C-9D89-71A70410D835}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C4DBA89-3D17-451C-9D89-71A70410D835}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C4DBA89-3D17-451C-9D89-71A70410D835}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C4DBA89-3D17-451C-9D89-71A70410D835}.Release|Any CPU.Build.0 = Release|Any CPU
{F13BEF5A-5426-4F77-879F-A7F812F1E336}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F13BEF5A-5426-4F77-879F-A7F812F1E336}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F13BEF5A-5426-4F77-879F-A7F812F1E336}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F13BEF5A-5426-4F77-879F-A7F812F1E336}.Release|Any CPU.Build.0 = Release|Any CPU
{5BD3E03A-3086-4CF6-950B-215C1E76B7F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BD3E03A-3086-4CF6-950B-215C1E76B7F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BD3E03A-3086-4CF6-950B-215C1E76B7F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BD3E03A-3086-4CF6-950B-215C1E76B7F5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -194,7 +200,8 @@ Global
{ECEEEF09-5053-46D7-B7E8-2711B523D1F6} = {C0150C21-3EC8-4973-B57C-26BC27A4ED57}
{93C07A18-89BA-45A2-B818-F2B20E17E0BE} = {C0150C21-3EC8-4973-B57C-26BC27A4ED57}
{3078BCDB-FC35-4826-82A1-9CE094446DE8} = {7515BF2B-C485-43A0-AB2F-DC761B6AB151}
{6C4DBA89-3D17-451C-9D89-71A70410D835} = {2AA826A1-5647-46B2-9885-20E89050653F}
{F13BEF5A-5426-4F77-879F-A7F812F1E336} = {C0150C21-3EC8-4973-B57C-26BC27A4ED57}
{5BD3E03A-3086-4CF6-950B-215C1E76B7F5} = {7515BF2B-C485-43A0-AB2F-DC761B6AB151}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3577CC82-8B1B-4B06-829A-3E1CCE78A2C3}

Просмотреть файл

@ -110,6 +110,35 @@ docker run --detach --rm \
    sh -c 'oryx create-script -appPath /app && /run.sh'
```
## Build Server Invocation
1. Build the Oryx solution
1. ![Build Solutionpng](doc/buildServer/buildSolution.png)
1. Create image with oryx and platform binaries
1. `time build/buildBuildImages.sh -t ltsversion`
1. Run docker to port map, volume mount a directory, specify the image with `oryx build`, and invoke BuildServer
1. ```bash
docker run -it -p 8086:80 \
-v C:\Repo\Oryx\tests\SampleApps\:/tmp/SampleApps \
-e "ASPNETCORE_URLS=http://+80" \
oryxdevmcr.azurecr.io/public/oryx/build:lts-versions \
/opt/buildscriptgen/BuildServer
```
![Start](doc/buildServer/start.png)
1. Invoke build
1. ![Post](doc/buildServer/post.png)
1. Under the hood `oryx build` is invoked
```bash
oryx build [sourcePath] \
--platform [platform] \
--platform-version [version] \
--output [outputPath] \
--log-file [logPath]
```
1. Check build status with id `1`
1. ![Status](doc/buildServer/status.png)
1. Check server healthcheck
1. ![Health Check](doc/buildServer/healthCheck.png)
# Components
Oryx consists of a build image, a collection of runtime images, a build script generator, and a collection of

Двоичные данные
doc/buildServer/buildSolution.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.2 KiB

Двоичные данные
doc/buildServer/healthCheck.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
doc/buildServer/post.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 38 KiB

Двоичные данные
doc/buildServer/start.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 50 KiB

Двоичные данные
doc/buildServer/status.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 24 KiB

Просмотреть файл

@ -42,6 +42,7 @@
<ItemGroup>
<ProjectReference Include="..\BuildScriptGenerator\BuildScriptGenerator.csproj" />
<ProjectReference Include="..\BuildServer\BuildServer.csproj" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>..\..\build\FinalPublicKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonFlatFileDataStore" Version="2.2.3" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
<PackageReference Include="RunProcessAsTask" Version="1.2.4" />
<PackageReference Include="JsonFlatFileDataStore" Version="2.2.3" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BuildScriptGenerator.Common\BuildScriptGenerator.Common.csproj" />
<ProjectReference Include="..\BuildScriptGenerator\BuildScriptGenerator.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,63 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Oryx.BuildServer.Models;
using Microsoft.Oryx.BuildServer.Respositories;
using Microsoft.Oryx.BuildServer.Services;
using System;
using System.Threading.Tasks;
namespace Microsoft.Oryx.BuildServer.Controllers
{
[Route("api/builds")]
[ApiController]
public class BuildController : ControllerBase
{
private readonly IBuildService _buildService;
public BuildController(IBuildService buildService)
{
_buildService = buildService;
}
// GET api/<Builds>/5
[HttpGet("{id}")]
[ProducesResponseType(typeof(Build), StatusCodes.Status201Created)]
public async Task<IActionResult> Get(string id)
{
var build = await _buildService.GetBuild(id);
if (build == null)
{
return NotFound();
}
return Ok(build);
}
// POST api/<Builds>
[HttpPost]
[ProducesResponseType(typeof(Build), StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody] Build build)
{
try
{
var createdBuild = await _buildService.StartBuild(build);
string uri = String.Format("api/builds/{0}", createdBuild.Id);
return Created(uri, createdBuild);
}
catch (ServiceException ex)
{
return BadRequest(ex.Message);
}
}
// PUT api/<Builds>/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] Build build)
{
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Oryx.BuildServer.Models;
namespace Microsoft.Oryx.BuildServer.Controllers
{
[ApiController]
[Route("[controller]")]
public class HealthController : ControllerBase
{
private readonly ILogger<HealthController> _logger;
public HealthController(ILogger<HealthController> logger)
{
_logger = logger;
}
[HttpGet(Name = "health")]
public HealthStatus Get()
{
_logger.LogInformation("Health check ok");
return new HealthStatus
{
Status = "Ok"
};
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
namespace Microsoft.Oryx.BuildServer.Models
{
public class Build
{
public string Id { get; set; }
public string? Status { get; set; }
public string Platform { get; set; }
public string Version { get; set; }
public string SourcePath { get; set; }
public string OutputPath { get; set; }
public string LogPath { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
namespace Microsoft.Oryx.BuildServer.Models
{
public class HealthStatus
{
public string Status { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,31 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Oryx.BuildServer
{
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,37 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5541",
"sslPort": 0
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "build",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"BuildServer": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "build",
"environmentVariables": {
"DOTNET_USE_POLLING_FILE_WATCHER": "true",
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://0.0.0.0:5000"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/build",
"publishAllPorts": true
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using JsonFlatFileDataStore;
using Microsoft.Oryx.BuildServer.Models;
using Microsoft.Oryx.BuildServer.Respositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Oryx.BuildServer.Repositories
{
public class BuildRepository : IRepository
{
private readonly DataStore _store;
private readonly IDocumentCollection<Build> _collection;
public BuildRepository(DataStore store)
{
_store = store;
_collection = _store.GetCollection<Build>();
}
public async Task<IEnumerable<Build>> GetAll()
{
return _collection.Find(x => true);
}
public Build? GetById(string id)
{
var build = _collection.Find(x => x.Id == id).FirstOrDefault();
return build;
}
public async Task<Build> Insert(Build build)
{
if (GetById(build.Id) != null)
{
throw new IntegrityException(String.Format("Build with id {0} already present", build.Id));
}
if (await _collection.ReplaceOneAsync(build.Id, build, true))
return build;
throw new OperationFailedException("Insert Failed");
}
public async Task<Build> Update(Build build)
{
if(await _collection.UpdateOneAsync(build.Id, build))
return build;
throw new OperationFailedException("Update Failed");
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.Oryx.BuildServer.Repositories
{
public interface IRepository
{
public Task<Build> Insert(Build build);
public Task<IEnumerable<Build>> GetAll();
public Task<Build> Update(Build build);
public Build? GetById(string id);
}
}

Просмотреть файл

@ -0,0 +1,25 @@
using System;
namespace Microsoft.Oryx.BuildServer.Respositories
{
public class ServiceException : Exception
{
public ServiceException(string? message) : base(message)
{
}
}
public class IntegrityException : ServiceException
{
public IntegrityException(string? message) : base(message)
{
}
}
public class OperationFailedException : ServiceException
{
public OperationFailedException(string? message) : base(message)
{
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
namespace Microsoft.Oryx.BuildServer.Services.ArtifactBuilders
{
public class ArtifactBuilderFactory : IArtifactBuilderFactory
{
private IArtifactBuilder _builder;
public ArtifactBuilderFactory(IArtifactBuilder builder)
{
_builder = builder;
}
public IArtifactBuilder CreateArtifactBuilder(Build build)
{
return _builder;
}
}
}

Просмотреть файл

@ -0,0 +1,84 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Extensions.Logging;
using Microsoft.Oryx.BuildServer.Models;
using System;
using System.Diagnostics;
using System.IO;
namespace Microsoft.Oryx.BuildServer.Services.ArtifactBuilders
{
class FileOutputHandler
{
private StreamWriter _fileStream;
private readonly ILogger<Builder> _logger;
public FileOutputHandler(StreamWriter filestream, ILogger<Builder> logger)
{
_fileStream = filestream;
_logger = logger;
}
public void handle(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
{
_fileStream.Write(outLine.Data + "\n");
_logger.LogInformation(outLine.Data);
}
}
}
public class Builder : IArtifactBuilder
{
private readonly ILogger<Builder> _logger;
public Builder(ILogger<Builder> logger)
{
_logger = logger;
}
public bool Build(Build build)
{
var logFilePath = $"{build.LogPath}/{build.Id}.log";
var sourcePath = build.SourcePath;
var outputPath = $"{build.OutputPath}/{build.Id}";
Directory.CreateDirectory(outputPath);
var cmd = $"oryx build {sourcePath} --log-file {logFilePath} " +
$"--output {outputPath} --platform {build.Platform} " +
$"--platform-version {build.Version}";
var escapedArgs = cmd.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{cmd}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true
}
};
process.Start();
_logger.LogInformation($"Process has started for command: {cmd}");
var outputHandler = new FileOutputHandler(new StreamWriter(logFilePath), _logger);
process.OutputDataReceived += outputHandler.handle;
process.ErrorDataReceived += outputHandler.handle;
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode == 0)
{
return true;
}
else
{
return false;
}
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
using System;
namespace Microsoft.Oryx.BuildServer.Services.ArtifactBuilders
{
public interface IArtifactBuilder
{
bool Build(Build build);
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
namespace Microsoft.Oryx.BuildServer.Services.ArtifactBuilders
{
public interface IArtifactBuilderFactory
{
IArtifactBuilder CreateArtifactBuilder(Build build);
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
using Microsoft.Oryx.BuildServer.Services.ArtifactBuilders;
using System;
using System.Threading.Tasks;
namespace Microsoft.Oryx.BuildServer.Services
{
public class BuildRunner : IBuildRunner
{
public void RunInBackground(IArtifactBuilder builder, Build build, Callback successCallback, Callback failureCallback)
{
var t = Task.Run(() =>
{
try
{
if(builder.Build(build))
{
successCallback(build);
} else
{
failureCallback(build);
}
} catch (Exception ex) {
Console.WriteLine(ex);
}
}
);
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
using Microsoft.Oryx.BuildServer.Repositories;
using Microsoft.Oryx.BuildServer.Services.ArtifactBuilders;
using System.Threading.Tasks;
namespace Microsoft.Oryx.BuildServer.Services
{
public class BuildService : IBuildService
{
private readonly IRepository _buildRepository;
private readonly IArtifactBuilderFactory _artifactBuilderFactory;
private readonly IBuildRunner _buildRunner;
public BuildService(IRepository buildRepository, IArtifactBuilderFactory artifactBuilderFactory, IBuildRunner buildRunner)
{
_buildRepository = buildRepository;
_artifactBuilderFactory = artifactBuilderFactory;
_buildRunner = buildRunner;
}
public async Task<Build> StartBuild(Build build)
{
build.Status = "IN_PROGRESS";
await _buildRepository.Insert(build);
var artifactBuilder = _artifactBuilderFactory.CreateArtifactBuilder(build);
_buildRunner.RunInBackground(artifactBuilder, build, MarkCompleted, MarkFailed);
return build;
}
public async Task<Build> GetBuild(string id)
{
var build = _buildRepository.GetById(id);
return build;
}
public async Task<Build> MarkCancelled(Build build)
{
build.Status = "CANCELLED";
await _buildRepository.Update(build);
return build;
}
public async Task<Build> MarkCompleted(Build build)
{
build.Status = "COMPLETED";
await _buildRepository.Update(build);
return build;
}
public async Task<Build> MarkFailed(Build build)
{
build.Status = "FAILED";
await _buildRepository.Update(build);
return build;
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
using Microsoft.Oryx.BuildServer.Services.ArtifactBuilders;
using System.Threading.Tasks;
namespace Microsoft.Oryx.BuildServer.Services
{
public delegate Task<Build> Callback(Build build);
public interface IBuildRunner
{
void RunInBackground(IArtifactBuilder builder, Build build, Callback successCallback, Callback FailureCallback);
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.Oryx.BuildServer.Models;
using System.Threading.Tasks;
namespace Microsoft.Oryx.BuildServer.Services
{
public interface IBuildService
{
Task<Build> StartBuild(Build build);
Task<Build> MarkCompleted(Build build);
Task<Build> MarkCancelled(Build build);
Task<Build> MarkFailed(Build build);
Task<Build> GetBuild(string id);
}
}

Просмотреть файл

@ -0,0 +1,74 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using JsonFlatFileDataStore;
using Microsoft.Oryx.BuildServer.Repositories;
using Microsoft.Oryx.BuildServer.Services;
using Microsoft.Oryx.BuildServer.Services.ArtifactBuilders;
using System.IO;
namespace Microsoft.Oryx.BuildServer
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
string folderName = "/store";
if (!Directory.Exists(folderName))
{
Directory.CreateDirectory(folderName);
}
var store = new DataStore("/store/builds.json", keyProperty: "id");
services.AddHttpContextAccessor();
services.AddMvc();
services.AddSingleton<IRepository>(x => new BuildRepository(store));
services.AddScoped<IArtifactBuilder, Builder>();
services.AddScoped<IArtifactBuilderFactory, ArtifactBuilderFactory>();
services.AddScoped<IBuildRunner, BuildRunner>();
services.AddScoped<IBuildService, BuildService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddControllers();
}
// 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.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
// endpoints.MapControllerRoute(
// "default",
// pattern: "{controller=Build}/{action=Index}/{id?}");
});
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://+:80"
}
}
},
"AllowedHosts": "*"
}