diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..341d071
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+**/bin/
+**/obj/
+**/global.json
+**/Dockerfile*
+**/.dockerignore*
+**/*.user
+
diff --git a/.gitignore b/.gitignore
index b14f7ce..aa9d70d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
/AccountOwnerServer/obj
/AccountOwnerServer/bin
+/AccountOwnerServer/Projects
/Contracts/obj
/Contracts/bin
/Entities/obj
@@ -8,3 +9,14 @@
/LoggerService/bin
/Repository/obj
/Repository/bin
+/Tests/bin
+/Tests/obj
+publish
+/.vs
+/AccountOwnerServer/internallog.txt
+/Integration/obj
+/Integration/bin
+/AccountOwnerServer/*.user
+/Tests/*.user
+/Integration/*.user
+.vscode/settings.json
diff --git a/AccountOwnerServer.sln b/AccountOwnerServer.sln
index 7620715..fff8a13 100644
--- a/AccountOwnerServer.sln
+++ b/AccountOwnerServer.sln
@@ -1,7 +1,6 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26730.3
+VisualStudioVersion = 15.0.27130.2010
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccountOwnerServer", "AccountOwnerServer\AccountOwnerServer.csproj", "{73FB08B8-4033-4116-B3AC-EC8253B024D4}"
EndProject
@@ -11,34 +10,106 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoggerService", "LoggerServ
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Entities", "Entities\Entities.csproj", "{8CD97B3F-C616-469C-80A2-A1B366611487}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Repository\Repository.csproj", "{CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Repository", "Repository\Repository.csproj", "{CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{89CF5113-7F84-40B8-A81C-B918F00183AD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration", "Integration\Integration.csproj", "{35B4193F-04B2-4C75-BA9B-8221185D2F83}"
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(ProjectConfigurationPlatforms) = postSolution
{73FB08B8-4033-4116-B3AC-EC8253B024D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{73FB08B8-4033-4116-B3AC-EC8253B024D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {73FB08B8-4033-4116-B3AC-EC8253B024D4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {73FB08B8-4033-4116-B3AC-EC8253B024D4}.Debug|x64.Build.0 = Debug|Any CPU
+ {73FB08B8-4033-4116-B3AC-EC8253B024D4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {73FB08B8-4033-4116-B3AC-EC8253B024D4}.Debug|x86.Build.0 = Debug|Any CPU
{73FB08B8-4033-4116-B3AC-EC8253B024D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{73FB08B8-4033-4116-B3AC-EC8253B024D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {73FB08B8-4033-4116-B3AC-EC8253B024D4}.Release|x64.ActiveCfg = Release|Any CPU
+ {73FB08B8-4033-4116-B3AC-EC8253B024D4}.Release|x64.Build.0 = Release|Any CPU
+ {73FB08B8-4033-4116-B3AC-EC8253B024D4}.Release|x86.ActiveCfg = Release|Any CPU
+ {73FB08B8-4033-4116-B3AC-EC8253B024D4}.Release|x86.Build.0 = Release|Any CPU
{52725034-B791-4DF1-A227-02EDB5BF78E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52725034-B791-4DF1-A227-02EDB5BF78E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {52725034-B791-4DF1-A227-02EDB5BF78E0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {52725034-B791-4DF1-A227-02EDB5BF78E0}.Debug|x64.Build.0 = Debug|Any CPU
+ {52725034-B791-4DF1-A227-02EDB5BF78E0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {52725034-B791-4DF1-A227-02EDB5BF78E0}.Debug|x86.Build.0 = Debug|Any CPU
{52725034-B791-4DF1-A227-02EDB5BF78E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52725034-B791-4DF1-A227-02EDB5BF78E0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {52725034-B791-4DF1-A227-02EDB5BF78E0}.Release|x64.ActiveCfg = Release|Any CPU
+ {52725034-B791-4DF1-A227-02EDB5BF78E0}.Release|x64.Build.0 = Release|Any CPU
+ {52725034-B791-4DF1-A227-02EDB5BF78E0}.Release|x86.ActiveCfg = Release|Any CPU
+ {52725034-B791-4DF1-A227-02EDB5BF78E0}.Release|x86.Build.0 = Release|Any CPU
{A190AF01-84DC-40A8-BEF3-C015825C8B30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A190AF01-84DC-40A8-BEF3-C015825C8B30}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A190AF01-84DC-40A8-BEF3-C015825C8B30}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A190AF01-84DC-40A8-BEF3-C015825C8B30}.Debug|x64.Build.0 = Debug|Any CPU
+ {A190AF01-84DC-40A8-BEF3-C015825C8B30}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A190AF01-84DC-40A8-BEF3-C015825C8B30}.Debug|x86.Build.0 = Debug|Any CPU
{A190AF01-84DC-40A8-BEF3-C015825C8B30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A190AF01-84DC-40A8-BEF3-C015825C8B30}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A190AF01-84DC-40A8-BEF3-C015825C8B30}.Release|x64.ActiveCfg = Release|Any CPU
+ {A190AF01-84DC-40A8-BEF3-C015825C8B30}.Release|x64.Build.0 = Release|Any CPU
+ {A190AF01-84DC-40A8-BEF3-C015825C8B30}.Release|x86.ActiveCfg = Release|Any CPU
+ {A190AF01-84DC-40A8-BEF3-C015825C8B30}.Release|x86.Build.0 = Release|Any CPU
{8CD97B3F-C616-469C-80A2-A1B366611487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CD97B3F-C616-469C-80A2-A1B366611487}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8CD97B3F-C616-469C-80A2-A1B366611487}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8CD97B3F-C616-469C-80A2-A1B366611487}.Debug|x64.Build.0 = Debug|Any CPU
+ {8CD97B3F-C616-469C-80A2-A1B366611487}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8CD97B3F-C616-469C-80A2-A1B366611487}.Debug|x86.Build.0 = Debug|Any CPU
{8CD97B3F-C616-469C-80A2-A1B366611487}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CD97B3F-C616-469C-80A2-A1B366611487}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8CD97B3F-C616-469C-80A2-A1B366611487}.Release|x64.ActiveCfg = Release|Any CPU
+ {8CD97B3F-C616-469C-80A2-A1B366611487}.Release|x64.Build.0 = Release|Any CPU
+ {8CD97B3F-C616-469C-80A2-A1B366611487}.Release|x86.ActiveCfg = Release|Any CPU
+ {8CD97B3F-C616-469C-80A2-A1B366611487}.Release|x86.Build.0 = Release|Any CPU
{CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Debug|x64.Build.0 = Debug|Any CPU
+ {CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Debug|x86.Build.0 = Debug|Any CPU
{CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Release|x64.ActiveCfg = Release|Any CPU
+ {CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Release|x64.Build.0 = Release|Any CPU
+ {CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Release|x86.ActiveCfg = Release|Any CPU
+ {CC0B1C81-CB8A-412C-BD48-BCBC3F1A1CD0}.Release|x86.Build.0 = Release|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Debug|x64.Build.0 = Debug|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Debug|x86.Build.0 = Debug|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Release|x64.ActiveCfg = Release|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Release|x64.Build.0 = Release|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Release|x86.ActiveCfg = Release|Any CPU
+ {89CF5113-7F84-40B8-A81C-B918F00183AD}.Release|x86.Build.0 = Release|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Debug|x64.Build.0 = Debug|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Debug|x86.Build.0 = Debug|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Release|Any CPU.Build.0 = Release|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Release|x64.ActiveCfg = Release|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Release|x64.Build.0 = Release|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Release|x86.ActiveCfg = Release|Any CPU
+ {35B4193F-04B2-4C75-BA9B-8221185D2F83}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/AccountOwnerServer/AccountOwnerServer.csproj b/AccountOwnerServer/AccountOwnerServer.csproj
index beffa6b..ec84190 100644
--- a/AccountOwnerServer/AccountOwnerServer.csproj
+++ b/AccountOwnerServer/AccountOwnerServer.csproj
@@ -9,8 +9,9 @@
-
+
+
@@ -24,4 +25,13 @@
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
diff --git a/AccountOwnerServer/Controllers/AccountController.cs b/AccountOwnerServer/Controllers/AccountController.cs
new file mode 100644
index 0000000..4404b93
--- /dev/null
+++ b/AccountOwnerServer/Controllers/AccountController.cs
@@ -0,0 +1,67 @@
+using Contracts;
+using Entities.Extensions;
+using Entities.Models;
+using Microsoft.AspNetCore.Mvc;
+using System;
+
+namespace AccountOwnerServer.Controllers
+{
+ [Route("api/[controller]")]
+ public class AccountController : Controller
+ {
+ private ILoggerManager _logger;
+ private IRepositoryWrapper _repository;
+
+ public AccountController(ILoggerManager logger, IRepositoryWrapper repository)
+ {
+ _logger = logger;
+ _repository = repository;
+ }
+
+ [HttpGet]
+ public IActionResult GetAllAccounts()
+ {
+ try
+ {
+ var accounts = _repository.Account.GetAllAccounts();
+
+ _logger.LogInfo($"Returned all accounts from database.");
+
+ return Ok(accounts);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Something went wrong inside GetAllAccounts action: {ex}");
+ return StatusCode(500, "Internal server error");
+ }
+ }
+
+ [HttpPost]
+ public IActionResult CreateOwner([FromBody]Account account)
+ {
+ try
+ {
+ if (account.IsObjectNull())
+ {
+ _logger.LogError("Object sent from client is null.");
+ return BadRequest("Object is null");
+ }
+
+ if (!ModelState.IsValid)
+ {
+ _logger.LogError("Invalid object sent from client.");
+ return BadRequest("Invalid model object");
+ }
+
+ _repository.Account.CreateAccount(account);
+
+ return CreatedAtRoute("AccountById", new { id = account.Id }, account);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Something went wrong inside CreateAccount action: {ex}");
+ return StatusCode(500, "Internal server error");
+ }
+ }
+ }
+}
diff --git a/AccountOwnerServer/Controllers/OwnerController.cs b/AccountOwnerServer/Controllers/OwnerController.cs
index dc2850c..e26f275 100644
--- a/AccountOwnerServer/Controllers/OwnerController.cs
+++ b/AccountOwnerServer/Controllers/OwnerController.cs
@@ -31,7 +31,7 @@ public IActionResult GetAllOwners()
}
catch (Exception ex)
{
- _logger.LogError($"Something went wrong inside GetAllOwners action: {ex.Message}");
+ _logger.LogError($"Something went wrong inside GetAllOwners action: {ex}");
return StatusCode(500, "Internal server error");
}
}
@@ -62,7 +62,7 @@ public IActionResult GetOwnerById(Guid id)
}
[HttpGet("{id}/account")]
- public IActionResult GetOwnerWithDetails(Guid id)
+ public IActionResult GetOwnerAccounts(Guid id)
{
try
{
diff --git a/AccountOwnerServer/Extensions/ServiceExtensions.cs b/AccountOwnerServer/Extensions/ServiceExtensions.cs
index 89ac3a5..ca6eb09 100644
--- a/AccountOwnerServer/Extensions/ServiceExtensions.cs
+++ b/AccountOwnerServer/Extensions/ServiceExtensions.cs
@@ -6,10 +6,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Repository;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
namespace AccountOwnerServer.Extensions
{
diff --git a/AccountOwnerServer/Startup.cs b/AccountOwnerServer/Startup.cs
index 3a86fc4..fa74191 100644
--- a/AccountOwnerServer/Startup.cs
+++ b/AccountOwnerServer/Startup.cs
@@ -12,7 +12,7 @@
using Microsoft.AspNetCore.HttpOverrides;
using System.IO;
using NLog.Extensions.Logging;
-using Contracts;
+using Swashbuckle.AspNetCore.Swagger;
namespace AccountOwnerServer
{
@@ -39,6 +39,11 @@ public void ConfigureServices(IServiceCollection services)
services.ConfigureRepositoryWrapper();
services.AddMvc();
+
+ services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new Info { Title = "AccountOwner API", Version = "v1" });
+ });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
@@ -47,7 +52,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
}
-
+
app.UseCors("CorsPolicy");
app.UseForwardedHeaders(new ForwardedHeadersOptions
@@ -70,6 +75,13 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseStaticFiles();
app.UseMvc();
+
+ app.UseSwagger();
+
+ app.UseSwaggerUI(c =>
+ {
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "AccountOwner API V1");
+ });
}
}
}
diff --git a/AccountOwnerServer/appsettings.json b/AccountOwnerServer/appsettings.json
index 934ec46..5935f76 100644
--- a/AccountOwnerServer/appsettings.json
+++ b/AccountOwnerServer/appsettings.json
@@ -13,6 +13,6 @@
}
},
"mysqlconnection": {
- "connectionString": "server=localhost;userid=dbuser;password=pass;database=accountowner;"
+ "connectionString": "server=db;port=3306;userid=dbuser;password=dbuserpassword;database=accountowner;"
}
}
diff --git a/AccountOwnerServer/nlog.config b/AccountOwnerServer/nlog.config
index 91c2174..69efc94 100644
--- a/AccountOwnerServer/nlog.config
+++ b/AccountOwnerServer/nlog.config
@@ -3,7 +3,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Trace"
- internalLogFile="d:Projects\Blog-AccountOwner\Project\internal_logs\internallog.txt">
+ internalLogFile="internallog.txt">
@@ -11,7 +11,7 @@
diff --git a/Contracts/IAccountRepository.cs b/Contracts/IAccountRepository.cs
index 825a4fb..945b7f5 100644
--- a/Contracts/IAccountRepository.cs
+++ b/Contracts/IAccountRepository.cs
@@ -1,8 +1,11 @@
using Entities.Models;
+using System.Collections.Generic;
namespace Contracts
{
public interface IAccountRepository
{
+ IEnumerable GetAllAccounts();
+ void CreateAccount(Account account);
}
}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..16e6204
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,27 @@
+FROM microsoft/aspnetcore-build as build-image
+
+WORKDIR /home/app
+
+COPY ./*.sln ./
+COPY ./*/*.csproj ./
+RUN for file in $(ls *.csproj); do mkdir -p ./${file%.*}/ && mv $file ./${file%.*}/; done
+
+RUN dotnet restore
+
+COPY . .
+
+RUN dotnet test --verbosity=normal --results-directory /TestResults/ --logger "trx;LogFileName=test_results.xml" ./Tests/Tests.csproj
+
+RUN dotnet publish ./AccountOwnerServer/AccountOwnerServer.csproj -o /publish/
+
+FROM microsoft/aspnetcore
+
+WORKDIR /publish
+
+COPY --from=build-image /publish .
+
+COPY --from=build-image /TestResults /TestResults
+
+ENV TEAMCITY_PROJECT_NAME = ${TEAMCITY_PROJECT_NAME}
+
+ENTRYPOINT ["dotnet", "AccountOwnerServer.dll"]
diff --git a/Dockerfile.Integration b/Dockerfile.Integration
new file mode 100644
index 0000000..88c3d4c
--- /dev/null
+++ b/Dockerfile.Integration
@@ -0,0 +1,15 @@
+FROM microsoft/dotnet:2-sdk
+
+WORKDIR /home/app
+
+COPY ./*.sln ./
+COPY ./*/*.csproj ./
+RUN for file in $(ls *.csproj); do mkdir -p ./${file%.*}/ && mv $file ./${file%.*}/; done
+
+RUN dotnet restore
+
+COPY . .
+
+WORKDIR /home/app/Integration/
+
+ENTRYPOINT ["dotnet", "test", "--verbosity=normal"]
\ No newline at end of file
diff --git a/Infrastructure/Registry/docker-compose.yml b/Infrastructure/Registry/docker-compose.yml
new file mode 100644
index 0000000..12a05cb
--- /dev/null
+++ b/Infrastructure/Registry/docker-compose.yml
@@ -0,0 +1,13 @@
+version: '3.0'
+
+services:
+ my-registry:
+ image: registry:latest
+ container_name: my-registry
+ volumes:
+ - registry:/var/lib/registry3
+ ports:
+ - "50000:5000"
+ restart: unless-stopped
+volumes:
+ registry:
\ No newline at end of file
diff --git a/Integration/Integration.csproj b/Integration/Integration.csproj
new file mode 100644
index 0000000..a4bd719
--- /dev/null
+++ b/Integration/Integration.csproj
@@ -0,0 +1,26 @@
+
+
+
+ netcoreapp2.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Integration/IntegrationTests.cs b/Integration/IntegrationTests.cs
new file mode 100644
index 0000000..ce62209
--- /dev/null
+++ b/Integration/IntegrationTests.cs
@@ -0,0 +1,47 @@
+using AccountOwnerServer;
+using Entities.Models;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Integration
+{
+ public class IntegrationTests
+ {
+ private readonly TestContext _context;
+
+ public IntegrationTests()
+ {
+ _context = new TestContext();
+ }
+
+ [Fact]
+ public async Task GetAllOwners_ReturnsOkResponse()
+ {
+ // Act
+ var response = await _context.Client.GetAsync("/api/owner");
+ response.EnsureSuccessStatusCode();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task GetAllOwners_ReturnsAListOfOwners()
+ {
+ // Act
+ var response = await _context.Client.GetAsync("/api/owner");
+ response.EnsureSuccessStatusCode();
+ var responseString = await response.Content.ReadAsStringAsync();
+ var owners = JsonConvert.DeserializeObject>(responseString);
+
+ // Assert
+ Assert.NotEmpty(owners);
+ }
+ }
+}
diff --git a/Integration/TestContext.cs b/Integration/TestContext.cs
new file mode 100644
index 0000000..33bc11b
--- /dev/null
+++ b/Integration/TestContext.cs
@@ -0,0 +1,37 @@
+using AccountOwnerServer;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Net.Http;
+
+namespace Integration
+{
+ public class TestContext: IDisposable
+ {
+ private TestServer _server;
+ public HttpClient Client { get; private set; }
+
+ public TestContext()
+ {
+ SetUpClient();
+ }
+
+ private void SetUpClient()
+ {
+ _server = new TestServer(new WebHostBuilder()
+ .UseConfiguration(new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json")
+ .Build())
+ .UseStartup());
+
+ Client = _server.CreateClient();
+ }
+
+ public void Dispose()
+ {
+ _server?.Dispose();
+ Client?.Dispose();
+ }
+ }
+}
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..74521ba
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,25 @@
+node('docker') {
+
+ stage 'Checkout'
+ checkout scm
+ stage 'Build & UnitTest'
+ sh "docker build -t accountownerapp:B${BUILD_NUMBER} -f Dockerfile ."
+ sh "docker build -t accountownerapp:test-B${BUILD_NUMBER} -f Dockerfile.Integration ."
+
+ stage 'Pusblish UT Reports'
+
+ containerID = sh (
+ script: "docker run -d accountownerapp:B${BUILD_NUMBER}",
+ returnStdout: true
+ ).trim()
+ echo "Container ID is ==> ${containerID}"
+ sh "docker cp ${containerID}:/TestResults/test_results.xml test_results.xml"
+ sh "docker stop ${containerID}"
+ sh "docker rm ${containerID}"
+ step([$class: 'MSTestPublisher', failOnError: false, testResultsFile: 'test_results.xml'])
+
+ stage 'Integration Test'
+ //sh 'docker-compose -f docker-compose.integration.yml up'
+ sh "docker-compose -f docker-compose.integration.yml up --force-recreate --abort-on-container-exit"
+ sh "docker-compose -f docker-compose.integration.yml down -v"
+}
diff --git a/README.md b/README.md
index 226a577..310e278 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,5 @@
# Docker Series
+https://code-maze.com/docker-series/
+
+## Part 8 of the Docker Series on CodeMaze blog
+https://code-maze.com/ci-jenkins-docker/
diff --git a/Repository/AccountRepository.cs b/Repository/AccountRepository.cs
index 9dcb2d5..0cd0be0 100644
--- a/Repository/AccountRepository.cs
+++ b/Repository/AccountRepository.cs
@@ -1,6 +1,9 @@
using Contracts;
using Entities;
using Entities.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
namespace Repository
{
@@ -10,5 +13,18 @@ public AccountRepository(RepositoryContext repositoryContext)
:base(repositoryContext)
{
}
+
+ public IEnumerable GetAllAccounts()
+ {
+ return FindAll()
+ .OrderBy(ac => ac.DateCreated);
+ }
+
+ public void CreateAccount(Account account)
+ {
+ account.Id = Guid.NewGuid();
+ Create(account);
+ Save();
+ }
}
}
diff --git a/Tests/OwnerRepositoryTests.cs b/Tests/OwnerRepositoryTests.cs
new file mode 100644
index 0000000..81d3133
--- /dev/null
+++ b/Tests/OwnerRepositoryTests.cs
@@ -0,0 +1,42 @@
+using Contracts;
+using Entities.Models;
+using Moq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Tests
+{
+ public class OwnerRepositoryTests
+ {
+ [Fact]
+ public void GetAllOwners_ReturnsListOfOwners_WithSingleOwner()
+ {
+ // Arrange
+ var mockRepo = new Mock();
+ mockRepo.Setup(repo => (repo.GetAllOwners())).Returns(GetOwners());
+
+ // Act
+ var result = mockRepo.Object.GetAllOwners().ToList();
+
+ // Assert
+ Assert.IsType>(result);
+ Assert.Single(result);
+ }
+
+ public List GetOwners()
+ {
+ return new List
+ {
+ new Owner
+ {
+ Id = Guid.NewGuid(),
+ Name = "John Keen",
+ DateOfBirth = new DateTime(1980, 12, 05),
+ Address = "61 Wellfield Road"
+ }
+ };
+ }
+ }
+}
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
new file mode 100644
index 0000000..114ad4b
--- /dev/null
+++ b/Tests/Tests.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netcoreapp2.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docker-compose-registry.yml b/docker-compose-registry.yml
new file mode 100644
index 0000000..c1b218b
--- /dev/null
+++ b/docker-compose-registry.yml
@@ -0,0 +1,8 @@
+version: '3.1'
+services:
+ my-registry:
+ ports:
+ - '5000:5000'
+ restart: always
+ container_name: my-registry
+ image: 'registry:2'
\ No newline at end of file
diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml
new file mode 100644
index 0000000..c84ac12
--- /dev/null
+++ b/docker-compose.ci.yml
@@ -0,0 +1,18 @@
+version: '3.1'
+services:
+ jenkins:
+ container_name: jenkins
+ ports:
+ - '8080:8080'
+ - '50000:50000'
+ image: localhost:5000/jenkins
+ jenkins-slave:
+ container_name: jenkins-slave
+ restart: always
+ environment:
+ - 'JENKINS_URL=http://jenkins:8080'
+ image: localhost:5000/jenkins-slave
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ depends_on:
+ - jenkins
\ No newline at end of file
diff --git a/docker-compose.integration.yml b/docker-compose.integration.yml
new file mode 100644
index 0000000..f91b0f6
--- /dev/null
+++ b/docker-compose.integration.yml
@@ -0,0 +1,32 @@
+version: '3.1'
+
+services:
+ db:
+ image: mysql:5.7
+ environment:
+ MYSQL_RANDOM_ROOT_PASSWORD: 1
+ MYSQL_DATABASE: accountowner
+ MYSQL_USER: dbuser
+ MYSQL_PASSWORD: dbuserpassword
+ DEBUG: 1
+ volumes:
+ - dbdata:/var/lib/mysql
+ - ./_MySQL_Init_Script:/docker-entrypoint-initdb.d
+ restart: always
+ accountownerapp:
+ depends_on:
+ - db
+ image: "accountownerapp:B${BUILD_NUMBER}"
+ build:
+ context: .
+ integration:
+ depends_on:
+ - accountownerapp
+ image: "accountownerapp:test-B${BUILD_NUMBER}"
+ build:
+ context: .
+ dockerfile: Dockerfile.Integration
+ environment:
+ - TEAMCITY_PROJECT_NAME
+volumes:
+ dbdata:
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..a591245
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,26 @@
+version: '3.0'
+
+services:
+ db:
+ image: mysql:5.7
+ environment:
+ MYSQL_RANDOM_ROOT_PASSWORD: 1
+ MYSQL_DATABASE: accountowner
+ MYSQL_USER: dbuser
+ MYSQL_PASSWORD: dbuserpassword
+ volumes:
+ - dbdata:/var/lib/mysql
+ - ./_MySQL_Init_Script:/docker-entrypoint-initdb.d
+ restart: always
+
+ accountownerapp:
+ depends_on:
+ - db
+ image: codemazeblog/accountownerapp
+ build:
+ context: .
+ ports:
+ - "8080:80"
+
+volumes:
+ dbdata:
\ No newline at end of file
diff --git a/jenkins-docker/master/Dockerfile b/jenkins-docker/master/Dockerfile
new file mode 100644
index 0000000..a7ecd22
--- /dev/null
+++ b/jenkins-docker/master/Dockerfile
@@ -0,0 +1,14 @@
+FROM jenkins/jenkins:latest
+
+RUN /usr/local/bin/install-plugins.sh git mstest matrix-auth workflow-aggregator docker-workflow blueocean credentials-binding
+
+ENV JENKINS_USER admin
+ENV JENKINS_PASS admin
+
+# Skip initial setup
+ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false
+
+COPY executors.groovy /usr/share/jenkins/ref/init.groovy.d/
+COPY default-user.groovy /usr/share/jenkins/ref/init.groovy.d/
+
+VOLUME /var/jenkins_home
diff --git a/jenkins-docker/master/default-user.groovy b/jenkins-docker/master/default-user.groovy
new file mode 100644
index 0000000..8d786fe
--- /dev/null
+++ b/jenkins-docker/master/default-user.groovy
@@ -0,0 +1,14 @@
+import jenkins.model.*
+import hudson.security.*
+
+def env = System.getenv()
+
+def jenkins = Jenkins.getInstance()
+jenkins.setSecurityRealm(new HudsonPrivateSecurityRealm(false))
+jenkins.setAuthorizationStrategy(new GlobalMatrixAuthorizationStrategy())
+
+def user = jenkins.getSecurityRealm().createAccount(env.JENKINS_USER, env.JENKINS_PASS)
+user.save()
+
+jenkins.getAuthorizationStrategy().add(Jenkins.ADMINISTER, env.JENKINS_USER)
+jenkins.save()
diff --git a/jenkins-docker/master/executors.groovy b/jenkins-docker/master/executors.groovy
new file mode 100644
index 0000000..0f6f064
--- /dev/null
+++ b/jenkins-docker/master/executors.groovy
@@ -0,0 +1,2 @@
+import jenkins.model.*
+Jenkins.instance.setNumExecutors(0)
diff --git a/jenkins-docker/slave/Dockerfile b/jenkins-docker/slave/Dockerfile
new file mode 100644
index 0000000..ddcaccf
--- /dev/null
+++ b/jenkins-docker/slave/Dockerfile
@@ -0,0 +1,33 @@
+FROM ubuntu:16.04
+
+# Install Docker CLI
+RUN apt-get update && apt-get install -y apt-transport-https ca-certificates
+RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
+RUN echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable" > /etc/apt/sources.list.d/docker.list
+RUN apt-get update && apt-get install -y docker-ce --allow-unauthenticated
+
+RUN apt-get update && apt-get install -y openjdk-8-jre curl python python-pip git
+RUN easy_install jenkins-webapi
+
+# Get docker-compose in the agent container
+
+RUN curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
+RUN mkdir -p /home/jenkins
+RUN mkdir -p /var/lib/jenkins
+
+ADD slave.py /var/lib/jenkins/slave.py
+
+WORKDIR /home/jenkins
+
+ENV JENKINS_URL "http://jenkins"
+ENV JENKINS_SLAVE_ADDRESS ""
+ENV JENKINS_USER "admin"
+ENV JENKINS_PASS "admin"
+ENV SLAVE_NAME ""
+ENV SLAVE_SECRET ""
+ENV SLAVE_EXECUTORS "1"
+ENV SLAVE_LABELS "docker"
+ENV SLAVE_WORING_DIR ""
+ENV CLEAN_WORKING_DIR "true"
+
+CMD [ "python", "-u", "/var/lib/jenkins/slave.py" ]
diff --git a/jenkins-docker/slave/slave.py b/jenkins-docker/slave/slave.py
new file mode 100644
index 0000000..855d9f2
--- /dev/null
+++ b/jenkins-docker/slave/slave.py
@@ -0,0 +1,90 @@
+from jenkins import Jenkins, JenkinsError, NodeLaunchMethod
+import os
+import signal
+import sys
+import urllib
+import subprocess
+import shutil
+import requests
+import time
+
+slave_jar = '/var/lib/jenkins/slave.jar'
+slave_name = os.environ['SLAVE_NAME'] if os.environ['SLAVE_NAME'] != '' else 'docker-slave-' + os.environ['HOSTNAME']
+jnlp_url = os.environ['JENKINS_URL'] + '/computer/' + slave_name + '/slave-agent.jnlp'
+slave_jar_url = os.environ['JENKINS_URL'] + '/jnlpJars/slave.jar'
+print(slave_jar_url)
+process = None
+
+def clean_dir(dir):
+ for root, dirs, files in os.walk(dir):
+ for f in files:
+ os.unlink(os.path.join(root, f))
+ for d in dirs:
+ shutil.rmtree(os.path.join(root, d))
+
+def slave_create(node_name, working_dir, executors, labels):
+ j = Jenkins(os.environ['JENKINS_URL'], os.environ['JENKINS_USER'], os.environ['JENKINS_PASS'])
+ j.node_create(node_name, working_dir, num_executors = int(executors), labels = labels, launcher = NodeLaunchMethod.JNLP)
+
+def slave_delete(node_name):
+ j = Jenkins(os.environ['JENKINS_URL'], os.environ['JENKINS_USER'], os.environ['JENKINS_PASS'])
+ j.node_delete(node_name)
+
+def slave_download(target):
+ if os.path.isfile(slave_jar):
+ os.remove(slave_jar)
+
+ loader = urllib.URLopener()
+ loader.retrieve(os.environ['JENKINS_URL'] + '/jnlpJars/slave.jar', '/var/lib/jenkins/slave.jar')
+
+def slave_run(slave_jar, jnlp_url):
+ params = [ 'java', '-jar', slave_jar, '-jnlpUrl', jnlp_url ]
+ if os.environ['JENKINS_SLAVE_ADDRESS'] != '':
+ params.extend([ '-connectTo', os.environ['JENKINS_SLAVE_ADDRESS' ] ])
+
+ if os.environ['SLAVE_SECRET'] == '':
+ params.extend([ '-jnlpCredentials', os.environ['JENKINS_USER'] + ':' + os.environ['JENKINS_PASS'] ])
+ else:
+ params.extend([ '-secret', os.environ['SLAVE_SECRET'] ])
+ return subprocess.Popen(params, stdout=subprocess.PIPE)
+
+def signal_handler(sig, frame):
+ if process != None:
+ process.send_signal(signal.SIGINT)
+
+signal.signal(signal.SIGINT, signal_handler)
+signal.signal(signal.SIGTERM, signal_handler)
+
+def master_ready(url):
+ try:
+ r = requests.head(url, verify=False, timeout=None)
+ return r.status_code == requests.codes.ok
+ except:
+ return False
+
+while not master_ready(slave_jar_url):
+ print("Master not ready yet, sleeping for 10sec!")
+ time.sleep(10)
+
+slave_download(slave_jar)
+print 'Downloaded Jenkins slave jar.'
+
+if os.environ['SLAVE_WORING_DIR']:
+ os.setcwd(os.environ['SLAVE_WORING_DIR'])
+
+if os.environ['CLEAN_WORKING_DIR'] == 'true':
+ clean_dir(os.getcwd())
+ print "Cleaned up working directory."
+
+if os.environ['SLAVE_NAME'] == '':
+ slave_create(slave_name, os.getcwd(), os.environ['SLAVE_EXECUTORS'], os.environ['SLAVE_LABELS'])
+ print 'Created temporary Jenkins slave.'
+
+process = slave_run(slave_jar, jnlp_url)
+print 'Started Jenkins slave with name "' + slave_name + '" and labels [' + os.environ['SLAVE_LABELS'] + '].'
+process.wait()
+
+print 'Jenkins slave stopped.'
+if os.environ['SLAVE_NAME'] == '':
+ slave_delete(slave_name)
+ print 'Removed temporary Jenkins slave.'