();
38 | var sampler = new ConstSampler(sample: true);
39 |
40 | var reporter = new RemoteReporter.Builder()
41 | .WithLoggerFactory(loggerFactory)
42 | .WithSender(new UdpSender(Configuration["Widgetario:Tracing:Target"], 6831, 0))
43 | .Build();
44 |
45 | var tracer = new Tracer.Builder("Widgetario.Web")
46 | .WithLoggerFactory(loggerFactory)
47 | .WithSampler(sampler)
48 | .WithReporter(reporter)
49 | .Build();
50 |
51 | GlobalTracer.Register(tracer);
52 | return tracer;
53 | });
54 | services.AddOpenTracing();
55 | }
56 | else
57 | {
58 | services.AddSingleton(GlobalTracer.Instance);
59 | }
60 | }
61 |
62 |
63 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
64 | {
65 | if (env.IsDevelopment())
66 | {
67 | app.UseDeveloperExceptionPage();
68 | }
69 | else
70 | {
71 | app.UseExceptionHandler("/Error");
72 | }
73 |
74 | app.UseStaticFiles();
75 | app.UseRouting();
76 |
77 | app.UseMetricServer();
78 | app.UseHttpMetrics();
79 |
80 | app.UseAuthorization();
81 | app.UseEndpoints(endpoints =>
82 | {
83 | endpoints.MapControllerRoute(
84 | name: "default",
85 | pattern: "{controller=Home}/{action=Index}/{id?}");
86 | });
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model ProductViewModel
2 | @{
3 | ViewData["Title"] = "Products";
4 | }
5 |
6 | @if (ViewData["Theme"] != null)
7 | {
8 | var css = "/css/themes/" + ViewData["Theme"] + ".css";
9 |
10 | }
11 |
12 | @if (ViewData["Environment"] != null)
13 | {
14 |
15 |
16 |
@ViewData["Environment"]
17 |
18 |
19 | }
20 |
21 |
22 |
23 | @if (@Model.Products == null)
24 | {
25 | Loading...
26 | }
27 | else
28 | {
29 |
30 |
31 |
32 | Your widget
33 | Just
34 | Availability
35 |
36 |
37 |
38 | @foreach (var product in @Model.Products)
39 | {
40 |
41 | @product.Name
42 | @product.DisplayPrice
43 | @product.StockMessage
44 |
45 | }
46 |
47 |
48 | }
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model.ShowRequestId)
10 | {
11 |
12 | Request ID: @Model.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @ViewData["Title"] - Widgetario.Web
7 |
8 |
9 |
10 |
11 |
12 |
13 | @RenderBody()
14 |
15 |
16 |
17 |
18 |
19 | @RenderSection("Scripts", required: false)
20 |
21 |
22 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using Widgetario.Web
2 | @using Widgetario.Web.Models
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/Widgetario.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.File" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "File",
8 | "Args": { "path": "/logs/app.log" }
9 | }
10 | ],
11 | "Enrich": [ "FromLogContext", "WithMachineName" ],
12 | "Properties": {
13 | "Application": "Widgetario.Web"
14 | }
15 | },
16 | "AllowedHosts": "*",
17 | "Widgetario" :{
18 | "Theme": "light",
19 | "ProductsApi": {
20 | "Url": "http://localhost:8080/products"
21 | },
22 | "StockApi": {
23 | "Url": "http://localhost:8088/stock"
24 | },
25 | "Tracing": {
26 | "Enabled": false,
27 | "Target": "jaeger"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/config/serilog.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.File" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "File",
8 | "Args": { "path": "/logs/app.log" }
9 | }
10 | ],
11 | "Enrich": [ "FromLogContext", "WithMachineName" ],
12 | "Properties": {
13 | "Application": "Widgetario.Web"
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | /* Provide sufficient contrast against white background */
11 | a {
12 | color: #0366d6;
13 | }
14 |
15 | .btn-primary {
16 | color: #fff;
17 | background-color: #1b6ec2;
18 | border-color: #1861ac;
19 | }
20 |
21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
22 | color: #fff;
23 | background-color: #1b6ec2;
24 | border-color: #1861ac;
25 | }
26 |
27 | /* Sticky footer styles
28 | -------------------------------------------------- */
29 | html {
30 | font-size: 14px;
31 | }
32 | @media (min-width: 768px) {
33 | html {
34 | font-size: 16px;
35 | }
36 | }
37 |
38 | .border-top {
39 | border-top: 1px solid #e5e5e5;
40 | }
41 | .border-bottom {
42 | border-bottom: 1px solid #e5e5e5;
43 | }
44 |
45 | .box-shadow {
46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
47 | }
48 |
49 | button.accept-policy {
50 | font-size: 1rem;
51 | line-height: inherit;
52 | }
53 |
54 | /* Sticky footer styles
55 | -------------------------------------------------- */
56 | html {
57 | position: relative;
58 | min-height: 100%;
59 | }
60 |
61 | body {
62 | /* Margin bottom by footer height */
63 | margin-bottom: 60px;
64 | }
65 |
66 | .footer {
67 | position: absolute;
68 | bottom: 0;
69 | width: 100%;
70 | white-space: nowrap;
71 | line-height: 60px; /* Vertically center the text there */
72 | }
73 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/css/themes/dark.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 22px;
3 | font-family: Georgia;
4 | background-color: darkslategray;
5 | color: #f9fffd;
6 | }
7 |
8 | .table {
9 | color: #f9fffd;
10 | }
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/css/themes/light.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 22px;
3 | font-family: Georgia;
4 | background-color: #f9fffd;
5 | color: darkslategray;
6 | }
7 |
8 | .table {
9 | color: darkslategray;
10 | }
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/img/logo.png
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/img/logo2-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/img/logo2-small.png
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/img/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/img/logo2.png
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your Javascript code.
5 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2018 Twitter, Inc.
4 | Copyright (c) 2011-2018 The Bootstrap Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2019 The Bootstrap Authors
4 | * Copyright 2011-2019 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .NET Foundation. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/lib/jquery-validation/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright Jörn Zaefferer
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/Widgetario.Web/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd Widgetario.Web
4 | dotnet publish -c Release -o /out Widgetario.Web.csproj --no-restore
--------------------------------------------------------------------------------
/hackathon/solution-part-1/web/dotnet/restore.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd Widgetario.Web
4 | dotnet restore
--------------------------------------------------------------------------------
/hackathon/solution-part-2/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | products-db:
4 | image: hackathon/products-db
5 | networks:
6 | - app-net
7 |
8 | products-api:
9 | image: hackathon/products-api
10 | networks:
11 | - app-net
12 |
13 | stock-api:
14 | image: hackathon/stock-api
15 | networks:
16 | - app-net
17 |
18 | web:
19 | image: hackathon/web
20 | ports:
21 | - "8080:80"
22 | networks:
23 | - app-net
24 |
25 | networks:
26 | app-net:
--------------------------------------------------------------------------------
/hackathon/solution-part-3/config/web/logging.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/hackathon/solution-part-3/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | products-db:
4 | image: hackathon/products-db
5 | ports:
6 | - "5432:5432"
7 | networks:
8 | - app-net
9 |
10 | products-api:
11 | image: hackathon/products-api
12 | environment:
13 | - PRICE_FACTOR=1.5
14 | ports:
15 | - "8081:80"
16 | networks:
17 | - app-net
18 |
19 | stock-api:
20 | image: hackathon/stock-api
21 | ports:
22 | - "8082:8080"
23 | networks:
24 | - app-net
25 |
26 | web:
27 | image: hackathon/web
28 | environment:
29 | - Widgetario__Theme=dark
30 | volumes:
31 | - ./config/web:/app/config
32 | - ./logs/web:/logs
33 | ports:
34 | - "8080:80"
35 | networks:
36 | - app-net
37 |
38 | networks:
39 | app-net:
--------------------------------------------------------------------------------
/hackathon/solution-part-4/config/web/logging.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/hackathon/solution-part-4/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | products-db:
4 | image: hackathon/products-db
5 | ports:
6 | - "5432:5432"
7 | networks:
8 | - app-net
9 | restart: always
10 | scale: 1
11 | cpus: 1
12 | mem_limit: 250m
13 |
14 | products-api:
15 | image: hackathon/products-api
16 | environment:
17 | - PRICE_FACTOR=1.5
18 | networks:
19 | - app-net
20 | depends_on:
21 | - products-db
22 | restart: always
23 | scale: 2
24 | cpus: 0.5
25 | mem_limit: 400m
26 |
27 | stock-api:
28 | image: hackathon/stock-api
29 | networks:
30 | - app-net
31 | depends_on:
32 | - products-db
33 | restart: always
34 | scale: 3
35 | cpus: 0.25
36 | mem_limit: 100m
37 |
38 | web:
39 | image: hackathon/web
40 | environment:
41 | - Widgetario__Theme=dark
42 | volumes:
43 | - ./config/web:/app/config
44 | - ./logs/web:/logs
45 | ports:
46 | - "8080:80"
47 | networks:
48 | - app-net
49 | depends_on:
50 | - products-api
51 | - stock-api
52 | restart: always
53 | scale: 1
54 | cpus: 0.5
55 | mem_limit: 300m
56 |
57 | networks:
58 | app-net:
--------------------------------------------------------------------------------
/hackathon/src/db/postgres/Dockerfile:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | # We use Postgres for the database, version 11.6 - it can use a minimal OS.
4 |
5 | # There's an init script in this folder which populates the reference data:
6 | # init-products-db.sh
7 |
8 | # Your base image should make it easy to run that script as part of the container startup.
--------------------------------------------------------------------------------
/hackathon/src/db/postgres/init-products-db.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
5 |
6 | CREATE TABLE public.products (
7 | id bigint NOT NULL,
8 | name character varying(255) NULL,
9 | price numeric(19,2) NULL,
10 | stock bigint NOT NULL
11 | );
12 |
13 | ALTER TABLE public.products ADD CONSTRAINT products_pkey PRIMARY KEY (id);
14 |
15 | INSERT INTO "public"."products" ("id", "name", "price", "stock")
16 | VALUES (1, 'Arm64 SoC', 30.00, 600);
17 |
18 | INSERT INTO "public"."products" ("id", "name", "price", "stock")
19 | VALUES (2, 'IoT breakout board', 8.00, 40);
20 |
21 | INSERT INTO "public"."products" ("id", "name", "price", "stock")
22 | VALUES (3, 'DAC extension board', 15.50, 750);
23 |
24 | INSERT INTO "public"."products" ("id", "name", "price", "stock")
25 | VALUES (4, 'Mars comms unit', 6000.00, 0);
26 |
27 | EOSQL
--------------------------------------------------------------------------------
/hackathon/src/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | products-db:
4 | image: hackathon/products-db
5 |
6 | products-api:
7 | image: hackathon/products-api
8 |
9 | stock-api:
10 | image: hackathon/stock-api
11 |
12 | web:
13 | image: hackathon/web
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/Dockerfile:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | # We use Maven version 3.6.3 for the build, with JDK 11.
4 |
5 | # Run restore.sh and then build.sh - you'll need to make
6 | # the files executable first with chmod +x .
7 |
8 | # Build output is a single JAR file:
9 | # /usr/src/api/target/products-api-0.1.0.jar
10 |
11 | # The app should run on OpenJDK 11.0.12, it can use a minimal OS.
12 |
13 | # We need to set two environment variables -
14 | # JRE_VERSION and APP_VERSION.
15 |
16 | # The startup command needs to run the JAR file from the build:
17 | # java -jar products-api-0.1.0.jar
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | mvn -B dependency:go-offline
4 | mvn package
5 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.widgetario
7 | products-api
8 | 0.1.0
9 |
10 |
11 | org.springframework.boot
12 | spring-boot-starter-parent
13 | 2.4.3
14 |
15 |
16 |
17 |
18 |
19 | org.springframework.boot
20 | spring-boot-starter-web
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-data-jpa
25 |
26 |
27 | org.springdoc
28 | springdoc-openapi-ui
29 | 1.4.5
30 |
31 |
32 | org.postgresql
33 | postgresql
34 | 42.2.16
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-starter-actuator
39 |
40 |
41 | io.micrometer
42 | micrometer-registry-prometheus
43 | 1.5.4
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-test
48 | test
49 |
50 |
51 | com.jayway.jsonpath
52 | json-path
53 | test
54 |
55 |
56 |
57 |
58 | 1.8
59 |
60 |
61 |
62 |
63 |
64 | org.springframework.boot
65 | spring-boot-maven-plugin
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/restore.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | mvn -B dependency:go-offline
4 | mvn package
5 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/src/main/java/com/widgetario/Application.java:
--------------------------------------------------------------------------------
1 | package widgetario.products;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.web.bind.annotation.RequestMapping;
7 | import org.springframework.web.bind.annotation.RestController;
8 |
9 | @SpringBootApplication
10 | @RestController
11 | public class Application {
12 |
13 | @Autowired
14 | ProductRepository repository;
15 |
16 | @RequestMapping("/")
17 | public String home() {
18 | return "Nothing to see here, try /products";
19 | }
20 |
21 | public static void main(String[] args) {
22 | SpringApplication.run(Application.class, args);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/src/main/java/com/widgetario/configuration/RegistryConfiguration.java:
--------------------------------------------------------------------------------
1 | package widgetario.products;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.context.annotation.EnableAspectJAutoProxy;
6 | import io.micrometer.core.aop.TimedAspect;
7 | import io.micrometer.core.instrument.MeterRegistry;
8 |
9 | @Configuration
10 | @EnableAspectJAutoProxy
11 | public class RegistryConfiguration {
12 |
13 | @Bean
14 | TimedAspect timedAspect(MeterRegistry registry) {
15 | return new TimedAspect(registry);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/src/main/java/com/widgetario/controllers/ProductsController.java:
--------------------------------------------------------------------------------
1 | package widgetario.products;
2 |
3 | import io.micrometer.core.annotation.Timed;
4 | import io.micrometer.core.instrument.MeterRegistry;
5 |
6 | import java.math.BigDecimal;
7 | import java.math.MathContext;
8 | import java.util.Arrays;
9 | import java.util.List;
10 |
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 |
14 | import org.springframework.beans.factory.annotation.Autowired;
15 | import org.springframework.beans.factory.annotation.Value;
16 | import org.springframework.web.bind.annotation.RequestMapping;
17 | import org.springframework.web.bind.annotation.RequestParam;
18 | import org.springframework.web.bind.annotation.RestController;
19 | import org.springframework.web.client.RestTemplate;
20 |
21 | @RestController
22 | public class ProductsController {
23 | private static final Logger log = LoggerFactory.getLogger(ProductsController.class);
24 |
25 | @Autowired
26 | ProductRepository repository;
27 |
28 | @Autowired
29 | MeterRegistry registry;
30 |
31 | @Value("${price.factor}")
32 | private String priceFactor;
33 |
34 | @RequestMapping("/products")
35 | @Timed()
36 | public List get() {
37 | log.debug("** GET /products called, using price factor: " + priceFactor);
38 | registry.counter("products_data_load_total", "status", "called").increment();
39 | List products = null;
40 | try {
41 | products = repository.findAll();
42 | BigDecimal factor = new BigDecimal(priceFactor);
43 | MathContext mc = new MathContext(2);
44 | for (Product p:products) {
45 |
46 | p.setPrice(p.getPrice().multiply(factor, mc));
47 | }
48 | }
49 | catch (Exception ex) {
50 | log.debug("** GET /products failed!");
51 | registry.counter("products_data_load_total", "status", "failure").increment();
52 | }
53 | return products;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/src/main/java/com/widgetario/models/Product.java:
--------------------------------------------------------------------------------
1 | package widgetario.products;
2 |
3 | import java.io.Serializable;
4 | import java.math.BigDecimal;
5 |
6 | import javax.persistence.Column;
7 | import javax.persistence.Entity;
8 | import javax.persistence.GeneratedValue;
9 | import javax.persistence.GenerationType;
10 | import javax.persistence.Id;
11 | import javax.persistence.Table;
12 |
13 | @Entity
14 | @Table(name = "products")
15 | public class Product implements Serializable {
16 |
17 | @Id
18 | @GeneratedValue(strategy = GenerationType.AUTO)
19 | private long id;
20 |
21 | @Column(name = "name")
22 | private String name;
23 |
24 | @Column(name = "price")
25 | private BigDecimal price;
26 |
27 | public Product() {}
28 |
29 | public Product(String name, BigDecimal price) {
30 | setName(name);
31 | setPrice(price);
32 | }
33 |
34 | public long getId() {
35 | return id;
36 | }
37 |
38 | public void setId(long id) {
39 | this.id = id;
40 | }
41 |
42 | public String getName() {
43 | return name;
44 | }
45 |
46 | public void setName(String name) {
47 | this.name = name;
48 | }
49 |
50 | public BigDecimal getPrice() {
51 | return price;
52 | }
53 |
54 | public void setPrice(BigDecimal price) {
55 | this.price = price;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/src/main/java/com/widgetario/repositories/ProductRepository.java:
--------------------------------------------------------------------------------
1 | package widgetario.products;
2 |
3 | import java.util.List;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import org.springframework.data.repository.CrudRepository;
9 |
10 | public interface ProductRepository extends CrudRepository {
11 | List findAll();
12 | }
13 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/src/main/java/com/widgetario/startup/ApplicationStartup.java:
--------------------------------------------------------------------------------
1 | package widgetario.products;
2 |
3 | import io.micrometer.core.instrument.MeterRegistry;
4 | import io.micrometer.core.instrument.Tags;
5 |
6 | import java.util.concurrent.atomic.AtomicInteger;
7 |
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.ApplicationArguments;
10 | import org.springframework.boot.ApplicationRunner;
11 | import org.springframework.stereotype.Component;
12 |
13 | @Component
14 | public class ApplicationStartup implements ApplicationRunner {
15 |
16 | private AtomicInteger appInfoGaugeValue = new AtomicInteger(1);
17 |
18 | @Autowired
19 | MeterRegistry registry;
20 |
21 | @Override
22 | public void run(ApplicationArguments args) throws Exception {
23 | registry.gauge("app.info", Tags.of("version", System.getenv("APP_VERSION"), "java.version", System.getenv("JRE_VERSION")), appInfoGaugeValue);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/hackathon/src/products-api/java/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | price.factor=${PRICE_FACTOR:1}
2 | logging.level.widgetario.products=DEBUG
3 | management.endpoints.web.exposure.include=prometheus
4 | server.port=80
5 | spring.jpa.show-sql=true
6 | spring.jpa.generate-ddl=true
7 | spring.jpa.hibernate.ddl-auto=update
8 | spring.jpa.database=POSTGRESQL
9 | spring.datasource.platform=postgres
10 | spring.datasource.url=jdbc:postgresql://products-db:5432/postgres
11 | spring.datasource.username=postgres
12 | spring.datasource.password=widgetario
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/Dockerfile:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | # We use Golang version 1.15.14 for the build, with the
4 | # environment variable CGO_ENABLED set to 0.
5 |
6 | # Run restore.sh and then build.sh - you'll need to make
7 | # the files executable first with chmod +x .
8 |
9 | # Build output is a single executable file:
10 | # /server
11 |
12 | # The app should run on a minimal OS.
13 |
14 | # We need to set four environment variables -
15 | # GOLANG_VERSION and APP_VERSION
16 | # CACHE_EXPIRY_SECONDS set to 45
17 | # POSTGRES_CONNECTION_STRING set to "host=products-db port=5432 user=postgres password=widgetario dbname=postgres sslmode=disable"
18 |
19 | # The startup command needs to run the executable from the build:
20 | # /server
21 |
22 | # We need to create an empty directory at /cache.
23 |
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd src
4 | go build -o /server
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/restore.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd src
4 | go mod download
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/src/go.mod:
--------------------------------------------------------------------------------
1 | module stock-api
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/gorilla/mux v1.8.0
7 | github.com/lib/pq v1.9.0
8 | github.com/prometheus/client_golang v1.9.0
9 | )
10 |
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/src/handlers/handlers.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "database/sql"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "net/http"
9 | "os"
10 | "strconv"
11 | "time"
12 | "github.com/gorilla/mux"
13 | _ "github.com/lib/pq"
14 | "stock-api/models"
15 | "io/ioutil"
16 | )
17 |
18 | type response struct {
19 | ID int64 `json:"id,omitempty"`
20 | Message string `json:"message,omitempty"`
21 | }
22 |
23 | func createConnection() *sql.DB {
24 | db, err := sql.Open("postgres", os.Getenv("POSTGRES_CONNECTION_STRING"))
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | err = db.Ping()
30 | if err != nil {
31 | panic(err)
32 | }
33 |
34 | return db
35 | }
36 |
37 | func GetHealth(w http.ResponseWriter, r *http.Request) {
38 | w.Write([]byte("OK"))
39 | }
40 |
41 | func GetProductStock(w http.ResponseWriter, r *http.Request) {
42 | params := mux.Vars(r)
43 | id,_ := strconv.Atoi(params["id"])
44 | var cachedProduct models.CachedProduct
45 | var cacheIsValid bool
46 |
47 | cacheFile := fmt.Sprintf("/cache/product-%v.json", id);
48 | if _, err := os.Stat(cacheFile); err == nil {
49 | content,_ := ioutil.ReadFile(cacheFile)
50 | _ = json.Unmarshal(content, &cachedProduct)
51 | cacheIsValid = time.Now().Unix() < cachedProduct.ExpiresAt
52 | if cacheIsValid {
53 | log.Printf("Loaded stock from cache for product ID: %v", id)
54 | } else {
55 | log.Printf("Cache expired for product ID: %v", id)
56 | os.Remove(cacheFile)
57 | }
58 | }
59 |
60 | if !cacheIsValid {
61 | product,_ := getProductStock(int64(id))
62 | log.Printf("Fetched stock from DB for product ID: %v", id)
63 | cacheExpiry,_ := strconv.Atoi(os.Getenv("CACHE_EXPIRY_SECONDS"))
64 | cachedProduct = models.CachedProduct{
65 | Product: product,
66 | ExpiresAt: time.Now().Unix() + int64(cacheExpiry),
67 | }
68 | data, _ := json.MarshalIndent(cachedProduct, "", " ")
69 | err := ioutil.WriteFile(cacheFile, data, 0644)
70 | if err != nil {
71 | log.Printf("ERR 1046 - failed to write to cache file: %v", cacheFile)
72 | }
73 | }
74 |
75 | w.Header().Add("Content-Type", "application/json")
76 | json.NewEncoder(w).Encode(cachedProduct.Product)
77 | }
78 |
79 | func SetProductStock(w http.ResponseWriter, r *http.Request) {
80 | params := mux.Vars(r)
81 | id,_ := strconv.Atoi(params["id"])
82 |
83 | var product models.Product
84 | err := json.NewDecoder(r.Body).Decode(&product)
85 | if err != nil {
86 | http.Error(w, err.Error(), http.StatusBadRequest)
87 | return
88 | }
89 |
90 | log.Printf("Setting stock to : %v, for product ID: %v", product.Stock, id)
91 | setProductStock(int64(id), product.Stock)
92 | log.Printf("Updated stock for product ID: %v", id)
93 |
94 | res := response{
95 | ID: int64(id),
96 | Message: "Stock updated",
97 | }
98 |
99 | w.Header().Add("Content-Type", "application/json")
100 | json.NewEncoder(w).Encode(res)
101 | }
102 |
103 | func getProductStock(id int64) (models.Product, error) {
104 | db := createConnection()
105 | defer db.Close()
106 |
107 | sql := `SELECT id, stock FROM "public"."products" WHERE id=$1`
108 | row := db.QueryRow(sql, id)
109 |
110 | var product models.Product
111 | err := row.Scan(&product.ID, &product.Stock)
112 |
113 | if err != nil {
114 | log.Fatalf("Error fetching product. %v", err)
115 | }
116 |
117 | return product, err
118 | }
119 |
120 | func setProductStock(id int64, stock int64) {
121 | db := createConnection()
122 | defer db.Close()
123 |
124 | sql := `UPDATE "public"."products" SET stock = $2 WHERE id=$1`
125 | _, err := db.Exec(sql, id, stock)
126 |
127 | if err != nil {
128 | log.Fatalf("Error updating product. %v", err)
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/src/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "os"
7 | "stock-api/router"
8 | "github.com/prometheus/client_golang/prometheus"
9 | "github.com/prometheus/client_golang/prometheus/promauto"
10 | )
11 |
12 | var (
13 | appInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
14 | Name: "app_info",
15 | Help: "Application info",
16 | }, []string{"version", "goversion"})
17 | )
18 |
19 | func main() {
20 | appInfo.WithLabelValues(os.Getenv("APP_VERSION"), os.Getenv("GOLANG_VERSION")).Set(1)
21 |
22 | r := router.Router()
23 | log.Println("Starting server on port 8080...")
24 | log.Fatal(http.ListenAndServe(":8080", r))
25 | }
26 |
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/src/middleware/prometheus.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gorilla/mux"
7 | "github.com/prometheus/client_golang/prometheus"
8 | "github.com/prometheus/client_golang/prometheus/promauto"
9 | )
10 |
11 | var (
12 | activeRequests = promauto.NewGauge(prometheus.GaugeOpts{
13 | Name: "http_requests_in_progress",
14 | Help: "Active HTTP requests",
15 | })
16 | )
17 |
18 | var (
19 | httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
20 | Name: "http_request_duration_seconds",
21 | Help: "Duration of HTTP requests",
22 | }, []string{"path"})
23 | )
24 |
25 | func Prometheus(next http.Handler) http.Handler {
26 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27 | activeRequests.Inc()
28 | route := mux.CurrentRoute(r)
29 | path, _ := route.GetPathTemplate()
30 | timer := prometheus.NewTimer(httpDuration.WithLabelValues(path))
31 | next.ServeHTTP(w, r)
32 | timer.ObserveDuration()
33 | activeRequests.Dec()
34 | })
35 | }
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/src/models/models.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Product struct {
4 | ID int64 `json:"id"`
5 | Stock int64 `json:"stock"`
6 | }
7 |
8 | type CachedProduct struct {
9 | Product Product `json:"product"`
10 | ExpiresAt int64 `json:"expiresAt"`
11 | }
--------------------------------------------------------------------------------
/hackathon/src/stock-api/golang/src/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "github.com/gorilla/mux"
5 | "github.com/prometheus/client_golang/prometheus/promhttp"
6 | "stock-api/handlers"
7 | "stock-api/middleware"
8 | )
9 |
10 | func Router() *mux.Router {
11 | router := mux.NewRouter()
12 | router.Use(middleware.Prometheus)
13 |
14 | router.Path("/metrics").Handler(promhttp.Handler())
15 | router.HandleFunc("/healthz", handlers.GetHealth).Methods("GET")
16 | router.HandleFunc("/stock/{id}", handlers.GetProductStock).Methods("GET", "OPTIONS")
17 | router.HandleFunc("/stock/{id}", handlers.SetProductStock).Methods("PUT", "OPTIONS")
18 |
19 | return router
20 | }
21 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Dockerfile:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | # We use .NET Core SDK version 3.1 for the build.
4 |
5 | # Run restore.sh and then build.sh - you'll need to make
6 | # the files executable first with chmod +x .
7 |
8 | # Build output is in the folder:
9 | # /out
10 |
11 | # The app should run using a .NET Core ASP.NET 3.1 image, with a minimal OS.
12 |
13 | # We need to set four environment variables -
14 | # DOTNET_VERSION and APP_VERSION
15 | # Widgetario__ProductsApi__Url set to "http://products-api/products"
16 | # Widgetario__StockApi__Url set to "http://stock-api:8080/stock"
17 |
18 | # The startup command needs to run the DLL from the build:
19 | # dotnet Widgetario.Web.dll
20 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Controllers/HomeController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Extensions.Logging;
4 | using OpenTracing;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using System.Net;
8 | using System.Threading.Tasks;
9 | using Widgetario.Web.Models;
10 | using Widgetario.Web.Services;
11 |
12 | namespace Widgetario.Web.Controllers
13 | {
14 | public class HomeController : Controller
15 | {
16 | private readonly IConfiguration _config;
17 | private readonly ITracer _tracer;
18 | private readonly ILogger _logger;
19 | private readonly ProductService _productsService;
20 | private readonly StockService _stockService;
21 |
22 | public HomeController(ProductService productsService, StockService stockService, ITracer tracer, IConfiguration config, ILogger logger)
23 | {
24 | _productsService = productsService;
25 | _stockService = stockService;
26 | _tracer = tracer;
27 | _config = config;
28 | _logger = logger;
29 | }
30 |
31 | public async Task Index()
32 | {
33 | var stopwatch = Stopwatch.StartNew();
34 | _logger.LogDebug($"Loading products & stock");
35 | var model = new ProductViewModel();
36 | using (var loadScope = _tracer.BuildSpan("api-load").StartActive())
37 | {
38 | using (var productLoadScope = _tracer.BuildSpan("product-api-load").StartActive())
39 | {
40 | model.Products = await _productsService.GetProducts();
41 | _logger.LogTrace($"Loaded: {model.Products.Count()} products from API");
42 | }
43 | foreach (var product in model.Products)
44 | {
45 | using (var stockLoadScope = _tracer.BuildSpan("stock-api-load").StartActive())
46 | {
47 | var productStock = await _stockService.GetStock(product.Id);
48 | product.Stock = productStock.Stock;
49 | _logger.LogTrace($"Fetched stock count: {product.Stock} for product ID: {product.Id} from API");
50 | }
51 | }
52 | if (model.Products.Sum(x=>x.Stock) == 0)
53 | {
54 | _logger.LogWarning("No stock for any products!");
55 | }
56 | _logger.LogDebug($"Products & stock load took: {stopwatch.Elapsed.TotalMilliseconds}ms");
57 | }
58 |
59 | if (_config.GetValue("Widgetario:Debug"))
60 | {
61 | ViewData["Environment"] = $"{_config["Widgetario:Environment"]} @ {Dns.GetHostName()}";
62 | }
63 | else
64 | {
65 | ViewData["Environment"] = $"{_config["Widgetario:Environment"]}";
66 | }
67 |
68 | ViewData["Theme"] = _config.GetValue("Widgetario:Theme") ?? "light";
69 |
70 | _logger.LogInformation($"Returning: {model.Products.Count()} products");
71 | return View(model);
72 | }
73 |
74 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
75 | public IActionResult Error()
76 | {
77 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Controllers/UpController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.Extensions.Logging;
3 |
4 | namespace Widgetario.Web.Controllers
5 | {
6 | public class UpController : Controller
7 | {
8 | private readonly ILogger _logger;
9 |
10 | public UpController(ILogger logger)
11 | {
12 | _logger = logger;
13 | }
14 |
15 | public IActionResult Index()
16 | {
17 | _logger.LogTrace($"/up called");
18 | return Ok();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Models/ErrorViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Widgetario.Web.Models
4 | {
5 | public class ErrorViewModel
6 | {
7 | public string RequestId { get; set; }
8 |
9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Models/Product.cs:
--------------------------------------------------------------------------------
1 | namespace Widgetario.Web.Models
2 | {
3 | public class Product
4 | {
5 | public long Id { get; set; }
6 |
7 | public string Name { get; set; }
8 |
9 | public double Price { get; set; }
10 |
11 | public int Stock { get; set; }
12 |
13 | public string StockMessage
14 | {
15 | get
16 | {
17 | var message = "Plenty";
18 | if (Stock == 0)
19 | {
20 | message = "SOLD OUT!";
21 | }
22 | else if (Stock < 50)
23 | {
24 | message = "Last few...";
25 | }
26 | return message;
27 | }
28 | }
29 |
30 |
31 | public string DisplayPrice
32 | {
33 | get
34 | {
35 | return $"${Price.ToString("#.00")}";
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Models/ProductStock.cs:
--------------------------------------------------------------------------------
1 | namespace Widgetario.Web.Models
2 | {
3 | public class ProductStock
4 | {
5 | public long Id { get; set; }
6 |
7 | public int Stock { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Models/ProductViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Widgetario.Web.Models
4 | {
5 | public class ProductViewModel
6 | {
7 | public IEnumerable Products { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 | using Prometheus;
10 | using Serilog;
11 |
12 | namespace Widgetario.Web
13 | {
14 | public class Program
15 | {
16 | private static readonly Gauge _InfoGauge =
17 | Metrics.CreateGauge("app_info", "Application info", "dotnet_version", "assembly_name", "app_version");
18 |
19 | public static void Main(string[] args)
20 | {
21 | var appVersion = Environment.GetEnvironmentVariable("APP_VERSION");
22 | var dotnetVersion = Environment.GetEnvironmentVariable("DOTNET_VERSION");
23 | _InfoGauge.Labels(dotnetVersion, "Widgetario.Web", appVersion).Set(1);
24 | CreateHostBuilder(args).Build().Run();
25 | }
26 |
27 | public static IHostBuilder CreateHostBuilder(string[] args) =>
28 | Host.CreateDefaultBuilder(args)
29 | .UseSerilog((builderContext, config) =>
30 | {
31 | config.ReadFrom.Configuration(builderContext.Configuration);
32 | })
33 | .ConfigureAppConfiguration((builderContext, config) =>
34 | {
35 | config.AddJsonFile("appsettings.json")
36 | .AddEnvironmentVariables()
37 | .AddJsonFile("config/serilog.json", optional: true, reloadOnChange: true)
38 | .AddJsonFile("config/logging.json", optional: true, reloadOnChange: true)
39 | .AddJsonFile("secrets/api.json", optional: true, reloadOnChange: true);
40 | })
41 | .ConfigureWebHostDefaults(webBuilder =>
42 | {
43 | webBuilder.UseStartup();
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:54605",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "Widgetario.Web": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "applicationUrl": "http://localhost:5000",
22 | "environmentVariables": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Services/ProductService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using RestSharp;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 | using Widgetario.Web.Models;
7 |
8 | namespace Widgetario.Web.Services
9 | {
10 | public class ProductService
11 | {
12 | private readonly IConfiguration _config;
13 |
14 | public string ApiUrl { get; private set; }
15 |
16 | public ProductService(IConfiguration config)
17 | {
18 | _config = config;
19 | ApiUrl = _config["Widgetario:ProductsApi:Url"];
20 | }
21 |
22 | public async Task> GetProducts()
23 | {
24 | var client = new RestClient(ApiUrl);
25 | var request = new RestRequest();
26 | var response = await client.ExecuteGetAsync>(request);
27 | if (!response.IsSuccessful)
28 | {
29 | throw new Exception($"Service call failed, status: {response.StatusCode}, message: {response.ErrorMessage}");
30 | }
31 | return response.Data;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Services/StockService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using RestSharp;
3 | using System;
4 | using System.Threading.Tasks;
5 | using Widgetario.Web.Models;
6 |
7 | namespace Widgetario.Web.Services
8 | {
9 | public class StockService
10 | {
11 | private readonly IConfiguration _config;
12 |
13 | public string ApiUrl { get; private set; }
14 |
15 | public StockService(IConfiguration config)
16 | {
17 | _config = config;
18 | ApiUrl = _config["Widgetario:StockApi:Url"];
19 | }
20 |
21 | public async Task GetStock(long productId)
22 | {
23 | var client = new RestClient(ApiUrl);
24 | var request = new RestRequest($"{productId}");
25 | var response = await client.ExecuteGetAsync(request);
26 | if (!response.IsSuccessful)
27 | {
28 | throw new Exception($"Service call failed, status: {response.StatusCode}, message: {response.ErrorMessage}");
29 | }
30 | return response.Data;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Startup.cs:
--------------------------------------------------------------------------------
1 | using Jaeger;
2 | using Jaeger.Reporters;
3 | using Jaeger.Samplers;
4 | using Jaeger.Senders.Thrift;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Hosting;
7 | using Microsoft.Extensions.Configuration;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.Extensions.Hosting;
10 | using Microsoft.Extensions.Logging;
11 | using OpenTracing;
12 | using OpenTracing.Util;
13 | using Prometheus;
14 | using Widgetario.Web.Services;
15 |
16 | namespace Widgetario.Web
17 | {
18 | public class Startup
19 | {
20 | public Startup(IConfiguration configuration)
21 | {
22 | Configuration = configuration;
23 | }
24 |
25 | public IConfiguration Configuration { get; }
26 |
27 | public void ConfigureServices(IServiceCollection services)
28 | {
29 | services.AddControllersWithViews();
30 | services.AddScoped();
31 | services.AddScoped();
32 |
33 | if (Configuration.GetValue("Widgetario:Tracing:Enabled"))
34 | {
35 | services.AddSingleton(serviceProvider =>
36 | {
37 | var loggerFactory = serviceProvider.GetRequiredService();
38 | var sampler = new ConstSampler(sample: true);
39 |
40 | var reporter = new RemoteReporter.Builder()
41 | .WithLoggerFactory(loggerFactory)
42 | .WithSender(new UdpSender(Configuration["Widgetario:Tracing:Target"], 6831, 0))
43 | .Build();
44 |
45 | var tracer = new Tracer.Builder("Widgetario.Web")
46 | .WithLoggerFactory(loggerFactory)
47 | .WithSampler(sampler)
48 | .WithReporter(reporter)
49 | .Build();
50 |
51 | GlobalTracer.Register(tracer);
52 | return tracer;
53 | });
54 | services.AddOpenTracing();
55 | }
56 | else
57 | {
58 | services.AddSingleton(GlobalTracer.Instance);
59 | }
60 | }
61 |
62 |
63 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
64 | {
65 | if (env.IsDevelopment())
66 | {
67 | app.UseDeveloperExceptionPage();
68 | }
69 | else
70 | {
71 | app.UseExceptionHandler("/Error");
72 | }
73 |
74 | app.UseStaticFiles();
75 | app.UseRouting();
76 |
77 | app.UseMetricServer();
78 | app.UseHttpMetrics();
79 |
80 | app.UseAuthorization();
81 | app.UseEndpoints(endpoints =>
82 | {
83 | endpoints.MapControllerRoute(
84 | name: "default",
85 | pattern: "{controller=Home}/{action=Index}/{id?}");
86 | });
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model ProductViewModel
2 | @{
3 | ViewData["Title"] = "Products";
4 | }
5 |
6 | @if (ViewData["Theme"] != null)
7 | {
8 | var css = "/css/themes/" + ViewData["Theme"] + ".css";
9 |
10 | }
11 |
12 | @if (ViewData["Environment"] != null)
13 | {
14 |
15 |
16 |
@ViewData["Environment"]
17 |
18 |
19 | }
20 |
21 |
22 |
23 | @if (@Model.Products == null)
24 | {
25 | Loading...
26 | }
27 | else
28 | {
29 |
30 |
31 |
32 | Your widget
33 | Just
34 | Availability
35 |
36 |
37 |
38 | @foreach (var product in @Model.Products)
39 | {
40 |
41 | @product.Name
42 | @product.DisplayPrice
43 | @product.StockMessage
44 |
45 | }
46 |
47 |
48 | }
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model.ShowRequestId)
10 | {
11 |
12 | Request ID: @Model.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @ViewData["Title"] - Widgetario.Web
7 |
8 |
9 |
10 |
11 |
12 |
13 | @RenderBody()
14 |
15 |
16 |
17 |
18 |
19 | @RenderSection("Scripts", required: false)
20 |
21 |
22 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using Widgetario.Web
2 | @using Widgetario.Web.Models
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/Widgetario.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.File" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "File",
8 | "Args": { "path": "/logs/app.log" }
9 | }
10 | ],
11 | "Enrich": [ "FromLogContext", "WithMachineName" ],
12 | "Properties": {
13 | "Application": "Widgetario.Web"
14 | }
15 | },
16 | "AllowedHosts": "*",
17 | "Widgetario" :{
18 | "ProductsApi": {
19 | "Url": "http://localhost:8080/products"
20 | },
21 | "StockApi": {
22 | "Url": "http://localhost:8088/stock"
23 | },
24 | "Tracing": {
25 | "Enabled": false,
26 | "Target": "jaeger"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/config/serilog.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.File" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "File",
8 | "Args": { "path": "/logs/app.log" }
9 | }
10 | ],
11 | "Enrich": [ "FromLogContext", "WithMachineName" ],
12 | "Properties": {
13 | "Application": "Widgetario.Web"
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | /* Provide sufficient contrast against white background */
11 | a {
12 | color: #0366d6;
13 | }
14 |
15 | .btn-primary {
16 | color: #fff;
17 | background-color: #1b6ec2;
18 | border-color: #1861ac;
19 | }
20 |
21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
22 | color: #fff;
23 | background-color: #1b6ec2;
24 | border-color: #1861ac;
25 | }
26 |
27 | /* Sticky footer styles
28 | -------------------------------------------------- */
29 | html {
30 | font-size: 14px;
31 | }
32 | @media (min-width: 768px) {
33 | html {
34 | font-size: 16px;
35 | }
36 | }
37 |
38 | .border-top {
39 | border-top: 1px solid #e5e5e5;
40 | }
41 | .border-bottom {
42 | border-bottom: 1px solid #e5e5e5;
43 | }
44 |
45 | .box-shadow {
46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
47 | }
48 |
49 | button.accept-policy {
50 | font-size: 1rem;
51 | line-height: inherit;
52 | }
53 |
54 | /* Sticky footer styles
55 | -------------------------------------------------- */
56 | html {
57 | position: relative;
58 | min-height: 100%;
59 | }
60 |
61 | body {
62 | /* Margin bottom by footer height */
63 | margin-bottom: 60px;
64 | }
65 |
66 | .footer {
67 | position: absolute;
68 | bottom: 0;
69 | width: 100%;
70 | white-space: nowrap;
71 | line-height: 60px; /* Vertically center the text there */
72 | }
73 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/css/themes/dark.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 22px;
3 | font-family: Georgia;
4 | background-color: darkslategray;
5 | color: #f9fffd;
6 | }
7 |
8 | .table {
9 | color: #f9fffd;
10 | }
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/css/themes/light.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 22px;
3 | font-family: Georgia;
4 | background-color: #f9fffd;
5 | color: darkslategray;
6 | }
7 |
8 | .table {
9 | color: darkslategray;
10 | }
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/img/logo.png
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/img/logo2-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/img/logo2-small.png
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/img/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/img/logo2.png
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your Javascript code.
5 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2018 Twitter, Inc.
4 | Copyright (c) 2011-2018 The Bootstrap Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2019 The Bootstrap Authors
4 | * Copyright 2011-2019 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .NET Foundation. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js:
--------------------------------------------------------------------------------
1 | // Unobtrusive validation support library for jQuery and jQuery Validate
2 | // Copyright (c) .NET Foundation. All rights reserved.
3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
4 | // @version v3.2.11
5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a(" ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),m.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive});
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/lib/jquery-validation/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright Jörn Zaefferer
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/Widgetario.Web/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd Widgetario.Web
4 | dotnet publish -c Release -o /out Widgetario.Web.csproj --no-restore
--------------------------------------------------------------------------------
/hackathon/src/web/dotnet/restore.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd Widgetario.Web
4 | dotnet restore
--------------------------------------------------------------------------------
/img/docker-desktop-kubernetes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/img/docker-desktop-kubernetes.png
--------------------------------------------------------------------------------
/img/widgetario-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/img/widgetario-architecture.png
--------------------------------------------------------------------------------
/img/widgetario-solution-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/img/widgetario-solution-1.png
--------------------------------------------------------------------------------
/img/widgetario-solution-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/img/widgetario-solution-2.png
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | Welcome to the Docker labs.
2 |
3 | These are hands-on resources to help you learn Docker.
4 |
5 | ## Pre-reqs
6 |
7 | - [Set up Docker and a Git client](./setup/README.md)
8 | - Create a [Docker Hub](https://hub.docker.com/signup) account (free)
9 | - Download the lab content
10 | - Open a terminal (PowerShell, Bash, ZSH or whatever you use)
11 | - Run: `git clone https://github.com/courselabs/docker`
12 | - Open the folder: `cd docker`
13 | - Log in to Docker Hub:
14 | - `docker login` - using your Docker Hub ID
15 | - _Optional_
16 | - Install [Visual Studio Code](https://code.visualstudio.com) (free - Windows, macOS and Linux) to browse the repo and documentation
17 |
18 |
19 | ## Part 1 - Containers and Images
20 |
21 | - [Running containers](labs/containers/README.md)
22 | - [Constructing the container environment](labs/env/README.md)
23 | - [Building images](labs/images/README.md)
24 | - [Using image registries](labs/registries/README.md)
25 |
26 | ## Part 2 - Multi-Container Applications
27 |
28 | - [Docker Compose](labs/compose/README.md)
29 | - [Modelling apps with Compose](labs/compose-model/README.md)
30 | - [Building apps with Compose](labs/compose-build/README.md)
31 | - [Limitations of Compose](labs/compose-limits/README.md)
32 |
33 | ## Part 3 - Advanced Docker
34 |
35 | - [Multi-stage builds](labs/multi-stage/README.md)
36 | - [Container networking](labs/networking/README.md)
37 | - [Understanding orchestration](labs/orchestration/README.md)
38 | - [Kubernetes 101](labs/kubernetes/README.md)
39 |
40 | ## Part 4 - Real-World Docker
41 |
42 | - [Troubleshooting](labs/troubleshooting/README.md)
43 | - [Hackathon!](hackathon/README.md)
44 |
45 | ### Credits
46 |
47 | Created by [@EltonStoneman](https://twitter.com/EltonStoneman) ([sixeyed](https://github.com/sixeyed)): Freelance Consultant and Trainer. Author of [Learn Docker in a Month of Lunches](https://www.manning.com/books/learn-docker-in-a-month-of-lunches), [Learn Kubernetes in a Month of Lunches](https://www.manning.com/books/learn-kubernetes-in-a-month-of-lunches) and [many Pluralsight courses](https://pluralsight.pxf.io/c/1197078/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fauthors%2Felton-stoneman).
--------------------------------------------------------------------------------
/labs/compose-build/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | Image tags are often used for application versions, and a [semantic versioning](https://semver.org) approach is very common.
4 |
5 | You might have multiple tags for the same image:
6 |
7 | - `1` - major version
8 | - `1.0` - major + minor version
9 | - `1.0.100` - major + minor version + build number
10 |
11 | That lets users choose to pin to a specific version - `1.0.100` will never change.
12 |
13 | Minor versions get updated with each build - `1.0` is `1.0.100` now but could be an alias for `1.0.126` next month.
14 |
15 | Major versions get updated with each build **and** each minor version update - `1` is `1.0.100` now, but it could be `1.2.407` next year.
16 |
17 | The RNG app uses a similar approach.
18 |
19 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/compose-build/rng/amd64.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05-linux-amd64
4 |
5 | rng-web:
6 | image: courselabs/rng-web:21.05-linux-amd64
7 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/args.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: ${REPOSITORY:-courselabs}/rng-api:${RELEASE:-21.05}-${BUILD_NUMBER:-0}
4 | build:
5 | args:
6 | BUILD_VERSION: ${RELEASE:-21.05}.${BUILD_NUMBER:-0}
7 | BUILD_TAG: ${GITHUB_WORKFLOW:-local}-${BUILD_NUMBER:-0}-${GITHUB_REF:-local}
8 | COMMIT_SHA: ${GITHUB_SHA:-local}
9 |
10 | rng-web:
11 | image: ${REPOSITORY:-courselabs}/rng-web:${RELEASE:-21.05}-${BUILD_NUMBER:-0}
12 | build:
13 | args:
14 | BUILD_VERSION: ${RELEASE:-21.05}.${BUILD_NUMBER:-0}
15 | BUILD_TAG: ${GITHUB_WORKFLOW:-local}-${BUILD_NUMBER:-0}-${GITHUB_REF:-local}
16 | COMMIT_SHA: ${GITHUB_SHA:-local}
--------------------------------------------------------------------------------
/labs/compose-build/rng/arm64.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:${RELEASE:-21.05}-linux-arm64
4 |
5 | rng-web:
6 | image: courselabs/rng-web:${RELEASE:-21.05}-linux-arm64
7 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/build.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | build:
4 | context: .
5 | dockerfile: docker/api/Dockerfile
6 |
7 | rng-web:
8 | build:
9 | context: .
10 | dockerfile: docker/web/Dockerfile
--------------------------------------------------------------------------------
/labs/compose-build/rng/compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | rng-api:
4 | image: courselabs/rng-api:21.05
5 |
6 | rng-web:
7 | image: courselabs/rng-web:21.05
8 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/config/prod/api/override.json:
--------------------------------------------------------------------------------
1 | {
2 | "Rng" : {
3 | "Range": {
4 | "Min": 0,
5 | "Max": 5000
6 | },
7 | "FailAfter": {
8 | "Enabled": false
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/labs/compose-build/rng/config/prod/logging.env:
--------------------------------------------------------------------------------
1 | Logging__LogLevel__Default=Information
--------------------------------------------------------------------------------
/labs/compose-build/rng/core.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: rng-api:21.05
4 | networks:
5 | - app-net
6 |
7 | rng-web:
8 | image: rng-web:21.05
9 | environment:
10 | - RngApi__Url=http://rng-api/rng
11 | networks:
12 | - app-net
13 |
14 | networks:
15 | app-net:
16 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/dev.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | environment:
4 | - Logging__LogLevel__Default=Debug
5 | ports:
6 | - "8089:80"
7 |
8 | rng-web:
9 | environment:
10 | - Logging__LogLevel__Default=Debug
11 | - RngApi__Url=http://rng-api/rng
12 | ports:
13 | - "8090:80"
14 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: rng-api:21.05-local
4 | networks:
5 | - app-net
6 | build:
7 | context: .
8 | dockerfile: docker/api/Dockerfile
9 |
10 | rng-web:
11 | image: rng-web:21.05-local
12 | environment:
13 | - RngApi__Url=http://rng-api/rng
14 | ports:
15 | - 8090:80
16 | networks:
17 | - app-net
18 | build:
19 | context: .
20 | dockerfile: docker/web/Dockerfile
21 |
22 | networks:
23 | app-net:
24 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/docker/api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS builder
2 | ARG BUILD_VERSION=1.0.0
3 |
4 | WORKDIR /src
5 | COPY src/Numbers.Api/Numbers.Api.csproj .
6 | RUN dotnet restore
7 |
8 | COPY src/Numbers.Api/ .
9 | RUN dotnet publish -c Release /p:Version=$BUILD_VERSION -o /out Numbers.Api.csproj
10 |
11 | # app image
12 | FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine
13 |
14 | ENTRYPOINT ["dotnet", "/app/Numbers.Api.dll"]
15 |
16 | WORKDIR /app
17 | COPY --from=builder /out/ .
18 |
19 | ARG BUILD_TAG=local
20 | ARG COMMIT_SHA=local
21 | LABEL build_tag=${BUILD_TAG}
22 | LABEL commit_sha=${COMMIT_SHA}
--------------------------------------------------------------------------------
/labs/compose-build/rng/docker/web/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS builder
2 | ARG BUILD_VERSION=1.0.0
3 |
4 | WORKDIR /src
5 | COPY src/Numbers.Web/Numbers.Web.csproj .
6 | RUN dotnet restore
7 |
8 | COPY src/Numbers.Web/ .
9 | RUN dotnet publish -c Release /p:Version=$BUILD_VERSION -o /out Numbers.Web.csproj
10 |
11 | # app image
12 | FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine
13 |
14 | ENV RngApi__Url=http://numbers-api/rng
15 |
16 | ENTRYPOINT ["dotnet", "/app/Numbers.Web.dll"]
17 |
18 | WORKDIR /app
19 | COPY --from=builder /out/ .
20 |
21 | ARG BUILD_TAG=local
22 | ARG COMMIT_SHA=local
23 | LABEL build_tag=${BUILD_TAG}
24 | LABEL commit_sha=${COMMIT_SHA}
--------------------------------------------------------------------------------
/labs/compose-build/rng/push-manifests.ps1:
--------------------------------------------------------------------------------
1 | $images=$(yq e '.services.[].image' compose.yml)
2 |
3 | foreach ($image in $images)
4 | {
5 | docker manifest create --amend $image `
6 | "$($image)-linux-arm64" `
7 | "$($image)-linux-amd64"
8 |
9 | docker manifest push $image
10 | }
11 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/release.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:${RELEASE:-21.05}
4 |
5 | rng-web:
6 | image: courselabs/rng-web:${RELEASE:-21.05}
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Api/Controllers/HealthController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace Numbers.Api.Controllers
4 | {
5 | [ApiController]
6 | [Route("[controller]")]
7 | public class HealthController : ControllerBase
8 | {
9 | [HttpGet]
10 | public IActionResult Get()
11 | {
12 | if (Status.Healthy)
13 | {
14 | return Ok("Ok");
15 | }
16 | else
17 | {
18 | return StatusCode(500);
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Api/Controllers/RngController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace Numbers.Api.Controllers
7 | {
8 | [ApiController]
9 | [Route("[controller]")]
10 | public class RngController : ControllerBase
11 | {
12 | private static Random _Random = new Random();
13 | private static int _CallCount;
14 |
15 | private readonly IConfiguration _config;
16 | private readonly ILogger _logger;
17 |
18 | public RngController(IConfiguration config, ILogger logger)
19 | {
20 | _config = config;
21 | _logger = logger;
22 | if (_CallCount == 0)
23 | {
24 | _logger.LogInformation("Random number generator initialized");
25 | }
26 | }
27 |
28 | [HttpGet]
29 | public IActionResult Get()
30 | {
31 | _CallCount++;
32 | if (_config.GetValue("Rng:FailAfter:Enabled") && _CallCount > _config.GetValue("Rng:FailAfter:CallCount"))
33 | {
34 | if (_config["Rng:FailAfter:Action"] == "Exit")
35 | {
36 | _logger.LogError($"FailAfter enabled. Call: {_CallCount}. Exiting.");
37 | Environment.Exit(100);
38 | }
39 | _logger.LogWarning($"FailAfter enabled. Call: {_CallCount}. Going unhealthy.");
40 | Status.Healthy = false;
41 | }
42 |
43 | if (Status.Healthy)
44 | {
45 | var min = _config.GetValue("Rng:Range:Min");
46 | var max = _config.GetValue("Rng:Range:Max");
47 | var n = _Random.Next(min, max);
48 | _logger.LogDebug($"Call: {_CallCount}. Returning random number: {n}, from min: {min}, max: {max}");
49 | return Ok(n);
50 | }
51 | else
52 | {
53 | _logger.LogWarning("Unhealthy!");
54 | return StatusCode(500);
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Api/Numbers.Api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Api/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Extensions.Hosting;
4 |
5 | namespace Numbers.Api
6 | {
7 | public class Program
8 | {
9 | public static void Main(string[] args)
10 | {
11 | CreateHostBuilder(args).Build().Run();
12 | }
13 |
14 | public static IHostBuilder CreateHostBuilder(string[] args) =>
15 | Host.CreateDefaultBuilder(args)
16 | .ConfigureAppConfiguration((builderContext, config) =>
17 | {
18 | config.AddJsonFile("config/logging.json", optional: true)
19 | .AddEnvironmentVariables()
20 | .AddJsonFile("config/override.json", optional: true);
21 | })
22 | .ConfigureWebHostDefaults(webBuilder =>
23 | {
24 | webBuilder.UseStartup();
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Api/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | namespace Numbers.Api
8 | {
9 | public class Startup
10 | {
11 | public Startup(IConfiguration configuration)
12 | {
13 | Configuration = configuration;
14 | }
15 |
16 | public IConfiguration Configuration { get; }
17 |
18 | public void ConfigureServices(IServiceCollection services)
19 | {
20 | services.AddControllers();
21 | }
22 |
23 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
24 | {
25 | if (env.IsDevelopment())
26 | {
27 | app.UseDeveloperExceptionPage();
28 | }
29 |
30 | app.UseRouting();
31 |
32 | app.UseEndpoints(endpoints =>
33 | {
34 | endpoints.MapControllers();
35 | });
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Api/Status.cs:
--------------------------------------------------------------------------------
1 | namespace Numbers.Api
2 | {
3 | public static class Status
4 | {
5 | public static bool Healthy { get; set; }
6 |
7 | static Status()
8 | {
9 | Healthy = true;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Api/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Api/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Warning"
7 | }
8 | },
9 | "AllowedHosts": "*",
10 | "Rng" : {
11 | "Range": {
12 | "Min": 0,
13 | "Max": 100
14 | },
15 | "FailAfter": {
16 | "Enabled": false,
17 | "CallCount" : 3,
18 | "Action" : "Exit"
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/App.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Sorry, there's nothing at this address.
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/Numbers.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/Pages/Error.razor:
--------------------------------------------------------------------------------
1 | @page "/error"
2 |
3 |
4 | Error.
5 | An error occurred while processing your request.
6 |
7 | Development Mode
8 |
9 | Swapping to Development environment will display more detailed information about the error that occurred.
10 |
11 |
12 | The Development environment shouldn't be enabled for deployed applications.
13 | It can result in displaying sensitive information from exceptions to end users.
14 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
15 | and restarting the app.
16 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/Pages/Index.razor:
--------------------------------------------------------------------------------
1 | @page "/"
2 |
3 | @using Numbers.Web.Services
4 | @inject RandomNumberService RngService
5 |
6 | Docker Course Labs Random Number Generator
7 |
8 | @if (callFailed){
9 | RNG service unavailable!
10 | }
11 |
12 | @if (callFailed == false && randomNumber != -1){
13 | Here it is: @randomNumber
14 | }
15 |
16 | Get a random number
17 |
18 | @code {
19 | bool callFailed = false;
20 | int randomNumber = -1;
21 |
22 | void GetRandomNumber()
23 | {
24 | callFailed = false;
25 | try
26 | {
27 | randomNumber = RngService.GetNumber();
28 | }
29 | catch
30 | {
31 | callFailed = true;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/Pages/_Host.cshtml:
--------------------------------------------------------------------------------
1 | @page "/"
2 | @namespace Numbers.Web.Pages
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
5 |
6 |
7 |
8 |
9 |
10 | Numbers.Web
11 |
12 |
13 |
14 |
15 |
16 |
17 | @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered))
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore;
7 | using Microsoft.AspNetCore.Hosting;
8 | using Microsoft.Extensions.Configuration;
9 | using Microsoft.Extensions.Hosting;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace Numbers.Web
13 | {
14 | public class Program
15 | {
16 | public static void Main(string[] args)
17 | {
18 | CreateHostBuilder(args).Build().Run();
19 | }
20 |
21 | public static IHostBuilder CreateHostBuilder(string[] args) =>
22 | Host.CreateDefaultBuilder(args)
23 | .ConfigureAppConfiguration((builderContext, config) =>
24 | {
25 | config.AddJsonFile("config/logging.json", optional: true)
26 | .AddEnvironmentVariables()
27 | .AddJsonFile("config/override.json", optional: true);
28 | })
29 | .ConfigureWebHostDefaults(webBuilder =>
30 | {
31 | webBuilder.UseStartup();
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/Services/RandomNumberService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.Logging;
5 | using RestSharp;
6 |
7 | namespace Numbers.Web.Services
8 | {
9 | public class RandomNumberService
10 | {
11 | private readonly IConfiguration _config;
12 | private readonly ILogger _logger;
13 |
14 | public RandomNumberService(IConfiguration config, ILogger logger)
15 | {
16 | _config = config;
17 | _logger = logger;
18 | _logger.LogInformation($"Using API at: {_config["RngApi:Url"]}");
19 | }
20 |
21 | public int GetNumber()
22 | {
23 | var client = new RestClient(_config["RngApi:Url"]);
24 | var request = new RestRequest();
25 | var response = client.Execute(request);
26 | if (!response.IsSuccessful)
27 | {
28 | throw new Exception("Service call failed");
29 | }
30 | return int.Parse(response.Content);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/Shared/MainLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 |
4 |
7 |
8 |
9 | @Body
10 |
11 |
12 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Components;
7 | using Microsoft.AspNetCore.Hosting;
8 | using Microsoft.AspNetCore.HttpsPolicy;
9 | using Microsoft.Extensions.Configuration;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using Microsoft.Extensions.Hosting;
12 | using Numbers.Web.Services;
13 |
14 | namespace Numbers.Web
15 | {
16 | public class Startup
17 | {
18 | public Startup(IConfiguration configuration)
19 | {
20 | Configuration = configuration;
21 | }
22 |
23 | public IConfiguration Configuration { get; }
24 |
25 | public void ConfigureServices(IServiceCollection services)
26 | {
27 | services.AddRazorPages();
28 | services.AddServerSideBlazor();
29 | services.AddSingleton();
30 | }
31 |
32 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
33 | {
34 | if (env.IsDevelopment())
35 | {
36 | app.UseDeveloperExceptionPage();
37 | }
38 | else
39 | {
40 | app.UseExceptionHandler("/Error");
41 | }
42 |
43 | app.UseStaticFiles();
44 | app.UseRouting();
45 |
46 | app.UseEndpoints(endpoints =>
47 | {
48 | endpoints.MapBlazorHub();
49 | endpoints.MapFallbackToPage("/_Host");
50 | });
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using System.Net.Http
2 | @using Microsoft.AspNetCore.Authorization
3 | @using Microsoft.AspNetCore.Components.Authorization
4 | @using Microsoft.AspNetCore.Components.Forms
5 | @using Microsoft.AspNetCore.Components.Routing
6 | @using Microsoft.AspNetCore.Components.Web
7 | @using Microsoft.JSInterop
8 | @using Numbers.Web
9 | @using Numbers.Web.Shared
10 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Warning"
7 | }
8 | },
9 | "AllowedHosts": "*",
10 | "RngApi": {
11 | "Url": "http://localhost:5000/rng"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/FONT-LICENSE:
--------------------------------------------------------------------------------
1 | SIL OPEN FONT LICENSE Version 1.1
2 |
3 | Copyright (c) 2014 Waybury
4 |
5 | PREAMBLE
6 | The goals of the Open Font License (OFL) are to stimulate worldwide
7 | development of collaborative font projects, to support the font creation
8 | efforts of academic and linguistic communities, and to provide a free and
9 | open framework in which fonts may be shared and improved in partnership
10 | with others.
11 |
12 | The OFL allows the licensed fonts to be used, studied, modified and
13 | redistributed freely as long as they are not sold by themselves. The
14 | fonts, including any derivative works, can be bundled, embedded,
15 | redistributed and/or sold with any software provided that any reserved
16 | names are not used by derivative works. The fonts and derivatives,
17 | however, cannot be released under any other type of license. The
18 | requirement for fonts to remain under this license does not apply
19 | to any document created using the fonts or their derivatives.
20 |
21 | DEFINITIONS
22 | "Font Software" refers to the set of files released by the Copyright
23 | Holder(s) under this license and clearly marked as such. This may
24 | include source files, build scripts and documentation.
25 |
26 | "Reserved Font Name" refers to any names specified as such after the
27 | copyright statement(s).
28 |
29 | "Original Version" refers to the collection of Font Software components as
30 | distributed by the Copyright Holder(s).
31 |
32 | "Modified Version" refers to any derivative made by adding to, deleting,
33 | or substituting -- in part or in whole -- any of the components of the
34 | Original Version, by changing formats or by porting the Font Software to a
35 | new environment.
36 |
37 | "Author" refers to any designer, engineer, programmer, technical
38 | writer or other person who contributed to the Font Software.
39 |
40 | PERMISSION & CONDITIONS
41 | Permission is hereby granted, free of charge, to any person obtaining
42 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
43 | redistribute, and sell modified and unmodified copies of the Font
44 | Software, subject to the following conditions:
45 |
46 | 1) Neither the Font Software nor any of its individual components,
47 | in Original or Modified Versions, may be sold by itself.
48 |
49 | 2) Original or Modified Versions of the Font Software may be bundled,
50 | redistributed and/or sold with any software, provided that each copy
51 | contains the above copyright notice and this license. These can be
52 | included either as stand-alone text files, human-readable headers or
53 | in the appropriate machine-readable metadata fields within text or
54 | binary files as long as those fields can be easily viewed by the user.
55 |
56 | 3) No Modified Version of the Font Software may use the Reserved Font
57 | Name(s) unless explicit written permission is granted by the corresponding
58 | Copyright Holder. This restriction only applies to the primary font name as
59 | presented to the users.
60 |
61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
62 | Software shall not be used to promote, endorse or advertise any
63 | Modified Version, except to acknowledge the contribution(s) of the
64 | Copyright Holder(s) and the Author(s) or with their explicit written
65 | permission.
66 |
67 | 5) The Font Software, modified or unmodified, in part or in whole,
68 | must be distributed entirely under this license, and must not be
69 | distributed under any other license. The requirement for fonts to
70 | remain under this license does not apply to any document created
71 | using the Font Software.
72 |
73 | TERMINATION
74 | This license becomes null and void if any of the above conditions are
75 | not met.
76 |
77 | DISCLAIMER
78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
86 | OTHER DEALINGS IN THE FONT SOFTWARE.
87 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/ICON-LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Waybury
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/README.md:
--------------------------------------------------------------------------------
1 | [Open Iconic v1.1.1](http://useiconic.com/open)
2 | ===========
3 |
4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons)
5 |
6 |
7 |
8 | ## What's in Open Iconic?
9 |
10 | * 223 icons designed to be legible down to 8 pixels
11 | * Super-light SVG files - 61.8 for the entire set
12 | * SVG sprite—the modern replacement for icon fonts
13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats
14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats
15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px.
16 |
17 |
18 | ## Getting Started
19 |
20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections.
21 |
22 | ### General Usage
23 |
24 | #### Using Open Iconic's SVGs
25 |
26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute).
27 |
28 | ```
29 |
30 | ```
31 |
32 | #### Using Open Iconic's SVG Sprite
33 |
34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack.
35 |
36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.*
37 |
38 | ```
39 |
40 |
41 |
42 | ```
43 |
44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions.
45 |
46 | ```
47 | .icon {
48 | width: 16px;
49 | height: 16px;
50 | }
51 | ```
52 |
53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag.
54 |
55 | ```
56 | .icon-account-login {
57 | fill: #f00;
58 | }
59 | ```
60 |
61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/).
62 |
63 | #### Using Open Iconic's Icon Font...
64 |
65 |
66 | ##### …with Bootstrap
67 |
68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}`
69 |
70 |
71 | ```
72 |
73 | ```
74 |
75 |
76 | ```
77 |
78 | ```
79 |
80 | ##### …with Foundation
81 |
82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}`
83 |
84 | ```
85 |
86 | ```
87 |
88 |
89 | ```
90 |
91 | ```
92 |
93 | ##### …on its own
94 |
95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}`
96 |
97 | ```
98 |
99 | ```
100 |
101 | ```
102 |
103 | ```
104 |
105 |
106 | ## License
107 |
108 | ### Icons
109 |
110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT).
111 |
112 | ### Fonts
113 |
114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web).
115 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.eot
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.otf
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.woff
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
2 |
3 | html, body {
4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
5 | }
6 |
7 | a, .btn-link {
8 | color: #0366d6;
9 | }
10 |
11 | .btn-primary {
12 | color: #fff;
13 | background-color: #1b6ec2;
14 | border-color: #1861ac;
15 | }
16 |
17 | app {
18 | position: relative;
19 | display: flex;
20 | flex-direction: column;
21 | }
22 |
23 | .top-row {
24 | height: 3.5rem;
25 | display: flex;
26 | align-items: center;
27 | }
28 |
29 | .main {
30 | flex: 1;
31 | }
32 |
33 | .main .top-row {
34 | background-color: #f7f7f7;
35 | border-bottom: 1px solid #d6d5d5;
36 | justify-content: flex-end;
37 | }
38 |
39 | .main .top-row > a {
40 | margin-left: 1.5rem;
41 | }
42 |
43 | .sidebar {
44 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
45 | }
46 |
47 | .sidebar .top-row {
48 | background-color: rgba(0,0,0,0.4);
49 | }
50 |
51 | .sidebar .navbar-brand {
52 | font-size: 1.1rem;
53 | }
54 |
55 | .sidebar .oi {
56 | width: 2rem;
57 | font-size: 1.1rem;
58 | vertical-align: text-top;
59 | top: -2px;
60 | }
61 |
62 | .nav-item {
63 | font-size: 0.9rem;
64 | padding-bottom: 0.5rem;
65 | }
66 |
67 | .nav-item:first-of-type {
68 | padding-top: 1rem;
69 | }
70 |
71 | .nav-item:last-of-type {
72 | padding-bottom: 1rem;
73 | }
74 |
75 | .nav-item a {
76 | color: #d7d7d7;
77 | border-radius: 4px;
78 | height: 3rem;
79 | display: flex;
80 | align-items: center;
81 | line-height: 3rem;
82 | }
83 |
84 | .nav-item a.active {
85 | background-color: rgba(255,255,255,0.25);
86 | color: white;
87 | }
88 |
89 | .nav-item a:hover {
90 | background-color: rgba(255,255,255,0.1);
91 | color: white;
92 | }
93 |
94 | .content {
95 | padding-top: 1.1rem;
96 | }
97 |
98 | .navbar-toggler {
99 | background-color: rgba(255, 255, 255, 0.1);
100 | }
101 |
102 | .valid.modified:not([type=checkbox]) {
103 | outline: 1px solid #26b050;
104 | }
105 |
106 | .invalid {
107 | outline: 1px solid red;
108 | }
109 |
110 | .validation-message {
111 | color: red;
112 | }
113 |
114 | @media (max-width: 767.98px) {
115 | .main .top-row {
116 | display: none;
117 | }
118 | }
119 |
120 | @media (min-width: 768px) {
121 | app {
122 | flex-direction: row;
123 | }
124 |
125 | .sidebar {
126 | width: 250px;
127 | height: 100vh;
128 | position: sticky;
129 | top: 0;
130 | }
131 |
132 | .main .top-row {
133 | position: sticky;
134 | top: 0;
135 | }
136 |
137 | .main > div {
138 | padding-left: 2rem !important;
139 | padding-right: 1.5rem !important;
140 | }
141 |
142 | .navbar-toggler {
143 | display: none;
144 | }
145 |
146 | .sidebar .collapse {
147 | /* Never collapse the sidebar for wide screens */
148 | display: block;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/labs/compose-build/rng/src/Numbers.Web/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/labs/compose-build/rng/src/Numbers.Web/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/labs/compose-build/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | The second part of the GitHub build uses the `latest` Docker Compose file:
4 |
5 | - [release.yml](./rng/release.yml) uses different image tags, with the `RELEASE` environment variable but not the `BUILD_NUMBER` variable the main Compose file uses
6 |
7 | When you merge in the latest file it will build images with the tag `21.05`, which is the version for this release of the app.
8 |
9 | Consumers can use `21.05` to get the current build for this release, or e.g. `21.05-57` to get a specific build:
10 |
11 | ```
12 | docker pull courselabs/rng-api:21.05
13 |
14 | docker image ls courselabs/rng-api
15 | ```
16 |
17 | Those two tags are aliases of the same image now, but with the next release the `21.05` tag will advance and will be an alias of the a later build.
18 |
19 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/compose-limits/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | Think about the ongoing lifecycle of your apps:
4 |
5 | - servers need to be taken offline for maintenance
6 |
7 | - operating system updates need to be deployed for your container images
8 |
9 | - application config changes over time
10 |
11 | - and hopefully your app gets more popular and you need to support more users.
12 |
13 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/compose-limits/rng/v1.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Rng__FailAfter__Enabled=true
6 | - Rng__FailAfter__CallCount=3
7 | - Rng__FailAfter__Action=Exit
8 | networks:
9 | - app-net
10 |
11 | rng-web:
12 | image: courselabs/rng-web:21.05
13 | environment:
14 | - Logging__LogLevel__Default=Debug
15 | - RngApi__Url=http://rng-api/rng
16 | ports:
17 | - "8088:80"
18 | networks:
19 | - app-net
20 |
21 | networks:
22 | app-net:
23 |
--------------------------------------------------------------------------------
/labs/compose-limits/rng/v2.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Rng__FailAfter__Enabled=true
6 | - Rng__FailAfter__CallCount=3
7 | - Rng__FailAfter__Action=Exit
8 | restart: always
9 | networks:
10 | - app-net
11 |
12 | rng-web:
13 | image: courselabs/rng-web:21.05
14 | environment:
15 | - Logging__LogLevel__Default=Debug
16 | - RngApi__Url=http://rng-api/rng
17 | restart: always
18 | ports:
19 | - "8088:80"
20 | networks:
21 | - app-net
22 |
23 | networks:
24 | app-net:
25 |
--------------------------------------------------------------------------------
/labs/compose-limits/rng/v3.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Rng__FailAfter__Enabled=true
6 | - Rng__FailAfter__CallCount=10
7 | - Rng__FailAfter__Action=Exit
8 | restart: always
9 | scale: 3
10 | networks:
11 | - app-net
12 |
13 | rng-web:
14 | image: courselabs/rng-web:21.05
15 | environment:
16 | - Logging__LogLevel__Default=Debug
17 | - RngApi__Url=http://rng-api/rng
18 | restart: always
19 | scale: 2
20 | ports:
21 | - "8088:80"
22 | networks:
23 | - app-net
24 |
25 | networks:
26 | app-net:
27 |
--------------------------------------------------------------------------------
/labs/compose-limits/rng/v4.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Rng__FailAfter__Enabled=true
6 | - Rng__FailAfter__CallCount=10
7 | - Rng__FailAfter__Action=Exit
8 | restart: always
9 | scale: 3
10 | cpus: 4
11 | mem_limit: 8g
12 | networks:
13 | - app-net
14 |
15 | rng-web:
16 | image: courselabs/rng-web:21.05
17 | environment:
18 | - Logging__LogLevel__Default=Debug
19 | - RngApi__Url=http://rng-api/rng
20 | restart: always
21 | scale: 1
22 | cpus: 32
23 | mem_limit: 64g
24 | ports:
25 | - "8088:80"
26 | networks:
27 | - app-net
28 |
29 | networks:
30 | app-net:
31 |
--------------------------------------------------------------------------------
/labs/compose-limits/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | It's all about having to run everything on a single server:
4 |
5 | - when the server goes offline - planned or unplanned - you lose all your apps
6 |
7 | - you'll update your container images regularly (at least monthly) to get OS and library updates, as well as new features. Updating your app means replacing containers, so there will be downtime while that happens because there are no other servers running additional containers
8 |
9 | - same with config, any changes get deployed by replacing containers which means your app can be offline - and may be broken when it comes back if there's a config mistake
10 |
11 | - your scaling options are limited to the CPU, memory and network ports on the machine. Most important is the port - only one container can listen on a port, so you can't run multiple copies of a public-facing component.
12 |
13 |
14 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/compose-model/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | The Compose CLI can be configured with environment variables, and you can set defaults for them in an [environment file](https://docs.docker.com/compose/env-file/).
4 |
5 | You'll need a new override file for the lab setup, then setting the default project name and the file locations in the environment file should get you there.
6 |
7 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/compose-model/lab/.env:
--------------------------------------------------------------------------------
1 | # compose configuration - default to dev:
2 | COMPOSE_PROJECT_NAME=rng-lab
3 | COMPOSE_PATH_SEPARATOR=;
4 | COMPOSE_FILE=./rng/core.yml;./rng/lab.yml
5 |
--------------------------------------------------------------------------------
/labs/compose-model/lab/compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | ports:
4 | - "8389:80"
5 |
6 | rng-web:
7 | ports:
8 | - "8390:80"
9 |
10 | secrets:
11 | dotnet-logging:
12 | file: ./config/dev/logging.json
13 | rng-api:
14 | file: ./config/dev/api/override.json
15 | rng-web:
16 | file: ./config/dev/web/override.json
17 |
--------------------------------------------------------------------------------
/labs/compose-model/rng/config/dev/api/override.json:
--------------------------------------------------------------------------------
1 | {
2 | "Rng" : {
3 | "Range": {
4 | "Min": 0,
5 | "Max": 50
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/labs/compose-model/rng/config/dev/logging.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging" : {
3 | "LogLevel" : {
4 | "Default" : "Debug"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/labs/compose-model/rng/config/dev/web/override.json:
--------------------------------------------------------------------------------
1 | {
2 | "RngApi" : {
3 | "Url" : "http://rng-api/rng"
4 | }
5 | }
--------------------------------------------------------------------------------
/labs/compose-model/rng/config/logging.env:
--------------------------------------------------------------------------------
1 | Logging__LogLevel__Default=Debug
--------------------------------------------------------------------------------
/labs/compose-model/rng/config/test/api/override.json:
--------------------------------------------------------------------------------
1 | {
2 | "Rng" : {
3 | "Range": {
4 | "Min": 0,
5 | "Max": 5000
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/labs/compose-model/rng/config/test/logging.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging" : {
3 | "LogLevel" : {
4 | "Default" : "Information"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/labs/compose-model/rng/config/test/web/override.json:
--------------------------------------------------------------------------------
1 | {
2 | "RngApi" : {
3 | "Url" : "http://rng-api/rng"
4 | }
5 | }
--------------------------------------------------------------------------------
/labs/compose-model/rng/core.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | secrets:
5 | - source: dotnet-logging
6 | target: /app/config/logging.json
7 | - source: rng-api
8 | target: /app/config/override.json
9 | networks:
10 | - app-net
11 |
12 | rng-web:
13 | image: courselabs/rng-web:21.05
14 | secrets:
15 | - source: dotnet-logging
16 | target: /app/config/logging.json
17 | - source: rng-web
18 | target: /app/config/override.json
19 | networks:
20 | - app-net
21 |
22 | networks:
23 | app-net:
24 |
--------------------------------------------------------------------------------
/labs/compose-model/rng/dev.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | ports:
4 | - "8189:80"
5 |
6 | rng-web:
7 | ports:
8 | - "8190:80"
9 |
10 | secrets:
11 | dotnet-logging:
12 | file: ./config/dev/logging.json
13 | rng-api:
14 | file: ./config/dev/api/override.json
15 | rng-web:
16 | file: ./config/dev/web/override.json
17 |
--------------------------------------------------------------------------------
/labs/compose-model/rng/test.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | ports:
4 | - "8289:80"
5 |
6 | rng-web:
7 | ports:
8 | - "8290:80"
9 |
10 | secrets:
11 | dotnet-logging:
12 | file: ./config/test/logging.json
13 | rng-api:
14 | file: ./config/test/api/override.json
15 | rng-web:
16 | file: ./config/test/web/override.json
17 |
--------------------------------------------------------------------------------
/labs/compose-model/rng/v1.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Logging__LogLevel__Default=Debug
6 | ports:
7 | - "8089:80"
8 | networks:
9 | - app-net
10 |
11 | rng-web:
12 | image: courselabs/rng-web:21.05
13 | environment:
14 | - Logging__LogLevel__Default=Debug
15 | - RngApi__Url=http://rng-api/rng
16 | ports:
17 | - "8090:80"
18 | networks:
19 | - app-net
20 |
21 | networks:
22 | app-net:
23 |
--------------------------------------------------------------------------------
/labs/compose-model/rng/v2.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | env_file: ./config/logging.env
5 | ports:
6 | - "8089:80"
7 | networks:
8 | - app-net
9 |
10 | rng-web:
11 | image: courselabs/rng-web:21.05
12 | env_file: ./config/logging.env
13 | environment:
14 | - RngApi__Url=http://rng-api/rng
15 | ports:
16 | - "8090:80"
17 | networks:
18 | - app-net
19 |
20 | networks:
21 | app-net:
--------------------------------------------------------------------------------
/labs/compose-model/rng/v3.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | volumes:
5 | - ./config/dev/logging.json:/app/config/logging.json
6 | secrets:
7 | - source: rng-api
8 | target: /app/config/override.json
9 | ports:
10 | - "8089:80"
11 | networks:
12 | - app-net
13 |
14 | rng-web:
15 | image: courselabs/rng-web:21.05
16 | volumes:
17 | - ./config/dev/logging.json:/app/config/logging.json
18 | secrets:
19 | - source: rng-web
20 | target: /app/config/override.json
21 | ports:
22 | - "8090:80"
23 | networks:
24 | - app-net
25 |
26 | networks:
27 | app-net:
28 |
29 | secrets:
30 | rng-api:
31 | file: ./config/dev/api/override.json
32 | rng-web:
33 | file: ./config/dev/web/override.json
34 |
--------------------------------------------------------------------------------
/labs/compose-model/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | The lab model for the app just sets new ports and uses the existing dev config files:
4 |
5 | - [lab/compose.yml](./lab/compose.yml)
6 |
7 | The Compose env file sets a project name and default files - using the core and the lab override:
8 |
9 | - [lab/.env](./lab/.env)
10 |
11 | Switch to the lab folder:
12 |
13 | ```
14 | cd labs/compose-model
15 | ```
16 |
17 | Copy in the sample solution:
18 |
19 | ```
20 | cp ./lab/.env .
21 | cp ./lab/compose.yml ./rng/lab.yml
22 | ```
23 |
24 | Start the app:
25 |
26 | ```
27 | docker-compose up -d
28 | ```
29 |
30 | > Try it out at http://localhost:8390
31 |
32 | Check API logs:
33 |
34 | ```
35 | docker logs rng-lab_rng-api_1
36 | ```
37 |
38 | > Shows the info and debug level logs
39 |
40 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/compose/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | Adding components to a Compose definition just means adding a new service block - and you can add another network block too.
4 |
5 | A service definition can have multiple network entries, but the network name used in the service needs to match a network defined in the Compose file.
6 |
7 | And you can look at the details of any Docker object by inspecting it.
8 |
9 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/compose/lab/compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Logging__LogLevel__Default=Debug
6 | ports:
7 | - "8089:80"
8 | networks:
9 | - app-net
10 |
11 | rng-web:
12 | image: courselabs/rng-web:21.05
13 | environment:
14 | - Logging__LogLevel__Default=Debug
15 | - RngApi__Url=http://rng-api/rng
16 | ports:
17 | - "8090:80"
18 | networks:
19 | - app-net
20 |
21 | nginx:
22 | image: nginx:alpine
23 | networks:
24 | - app-net
25 | - front-end
26 |
27 | networks:
28 | app-net:
29 | front-end:
30 |
--------------------------------------------------------------------------------
/labs/compose/nginx/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | nginx:
3 | image: nginx:1.21-alpine
4 | ports:
5 | - "8082:80"
--------------------------------------------------------------------------------
/labs/compose/rng/v1.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Logging__LogLevel__Default=Debug
6 | ports:
7 | - "8089:80"
8 | networks:
9 | - app-net
10 |
11 | rng-web:
12 | image: courselabs/rng-web:21.05
13 | environment:
14 | - Logging__LogLevel__Default=Debug
15 | ports:
16 | - "8090:80"
17 | networks:
18 | - app-net
19 |
20 | networks:
21 | app-net:
22 |
--------------------------------------------------------------------------------
/labs/compose/rng/v2.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Logging__LogLevel__Default=Debug
6 | ports:
7 | - "8089:80"
8 | networks:
9 | - app-net
10 |
11 | rng-web:
12 | image: courselabs/rng-web:21.05
13 | environment:
14 | - Logging__LogLevel__Default=Debug
15 | - RngApi__Url=http://rng-api/rng
16 | ports:
17 | - "8090:80"
18 | networks:
19 | - app-net
20 |
21 | networks:
22 | app-net:
23 |
--------------------------------------------------------------------------------
/labs/compose/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | The sample solution [lab/compose.yaml](./lab/compose.yml) adds:
4 |
5 | - a network called `front-end` with no options, so it will be created with the Docker defaults
6 | - a service called `nginx` which uses the Nginx image and connects to the `front-end` and `app-net` networks.
7 |
8 | Compose uses the directory name of the YAML file to identify the application - so to update the app, the Compose file needs to be copied to the `rng` folder.
9 |
10 | Copy the Compose file and deploy the update:
11 |
12 | ```
13 | cp ./labs/compose/lab/compose.yml ./labs/compose/rng/lab.yml
14 |
15 | docker-compose -f ./labs/compose/rng/lab.yml up -d
16 | ```
17 |
18 | > You'll see the new network and container created, but the RNG web and API containers will be left unchanged. The spec hasn't changed for those services, so the containers match the desired state.
19 |
20 | Inspect the new container to show the network details:
21 |
22 | ```
23 | docker inspect rng_nginx_1
24 | ```
25 |
26 | > You'll see it has two IP addresses in the network section at the end of the output. This is one IP from each network - like a machine with two network cards.
27 |
28 | Test connectivity from the Nginx container to the web container:
29 |
30 | ```
31 | docker exec rng_nginx_1 nslookup rng-web
32 | ```
33 |
34 | > The new container can resolve IP addresses for the original container.
35 |
36 |
37 | And from the web container to Nginx:
38 |
39 | ```
40 | docker exec rng_rng-web_1 ping -c3 nginx
41 | ```
42 |
43 | > The old containers can reach the new one.
44 |
45 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/containers/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | [Official images](https://hub.docker.com/search?q=java&type=image&image_filter=official) on Docker Hub are the place to start looking - they're packages which are maintained by the project team and quality-controlled by Docker.
4 |
5 | The Hub pages tell you the Docker command to run to download the container package.
6 |
7 | *Tags* are different variants you can use. You can filter by the version number and the runtime, but it will take some investigation. The *Alpine* Linux distro is usually the smallest you can find.
8 |
9 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/containers/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | Search for `java` on Docker Hub and the official image is the top hit, but open the page at https://hub.docker.com/_/java and you will see it's been deprecated.
4 |
5 | That means the `java` image (and the `openjdk` image which replaced it) are no longer being maintained. The images are old, so you should avoid them.
6 |
7 | There are a few alternatives with active maintainers (e.g. Amazon and IBM). My preference is for [eclipse-temurin](https://hub.docker.com/_/eclipse-temurin) which is an open-source build of OpenJDK.
8 |
9 | You might start by copying the command from the Docker Hub page, which will download the default image:
10 |
11 | ```
12 | docker pull eclipse-temurin
13 | ```
14 |
15 | Run a container to check the Java version:
16 |
17 | ```
18 | docker run eclipse-temurin java -version
19 | ```
20 |
21 | > The output will show the OpenJDK and JRE versions
22 |
23 | Check the [tags page on Docker Hub](https://hub.docker.com/_/eclipse-temurin/tags) and search for `alpine` and you'll see there are JRE and IDK versions.
24 |
25 | The JRE build for Alpine is the smallest - the tag contains the version number, so this pulls a small JRE image for OpenJDK 23:
26 |
27 | ```
28 | docker pull eclipse-temurin:23-jre-alpine
29 | ```
30 |
31 | Check the sizes:
32 |
33 | ```
34 | docker image ls eclipse-temurin
35 | ```
36 |
37 | > Your output may be different, but mine shows the default ("latest") image is 478MB; Alpin JRE is 206MB...
38 |
39 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/env/exercises.env:
--------------------------------------------------------------------------------
1 | COURSELABS=env
2 | LOG_LEVEL=debug
3 | FEATURES_CACHE_ENABLED=true
--------------------------------------------------------------------------------
/labs/env/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | The Docker CLI often uses the same names as Linux commands - in Linux you use `cp` to copy.
4 |
5 | Remember the docs are [here](https://docs.docker.com/engine/reference/commandline/cli/) - that should be enough :)
6 |
7 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/env/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Docker Course Labs
5 |
6 |
7 | Docker Course Labs
8 | Container Environment Lab
9 | The real content is at docker.courselabs.co
10 |
11 |
--------------------------------------------------------------------------------
/labs/env/scripts/print-network.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo ''
4 | echo '** Hostname **'
5 | hostname
6 | echo ''
7 |
8 | echo '** IP **'
9 | ip a s eth0 | grep 'inet ' | cut -d' ' -f6| cut -d/ -f1
10 | echo ''
11 |
12 | echo '** DNS **'
13 | cat /etc/resolv.conf | grep nameserver | cut -c 12-
14 | echo ''
--------------------------------------------------------------------------------
/labs/env/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | You can run a command inside the container to list files:
4 |
5 | ```
6 | docker exec tls ls /certs
7 | ```
8 |
9 | The [docker cp](https://docs.docker.com/engine/reference/commandline/cp/) command copies files between containers and the local filesystem.
10 |
11 | To copy from the container called `tls`, use:
12 |
13 | ```
14 | docker cp tls:/certs/server-cert.pem .
15 |
16 | docker cp tls:/certs/server-key.pem .
17 | ```
18 |
19 | You can also copy from the local machine into the container, but the target path needs to exist:
20 |
21 | ```
22 | docker exec tls mkdir /certs-backup
23 |
24 | docker cp server-cert.pem tls:/certs-backup/
25 |
26 | docker cp server-key.pem tls:/certs-backup/
27 |
28 | docker exec tls ls /certs-backup
29 | ```
30 |
31 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/images/base/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu
--------------------------------------------------------------------------------
/labs/images/curl/Dockerfile:
--------------------------------------------------------------------------------
1 | # Alpine is a tiny Linux distro
2 | FROM alpine
3 |
4 | # apk is the package manager for Alpine
5 | # this installs curl into the container image
6 | RUN apk add curl
7 |
8 | # tells Docker to run curl when it starts a container
9 | CMD curl
--------------------------------------------------------------------------------
/labs/images/curl/Dockerfile.v2:
--------------------------------------------------------------------------------
1 | # specifying an exact version makes the build repeatable
2 | FROM alpine:3.16
3 |
4 | # using the no-cache option creates a smaller image
5 | RUN apk add --no-cache curl=7.83.1-r3
6 |
7 | # entrypoint lets users add options when the container starts
8 | ENTRYPOINT ["curl"]
--------------------------------------------------------------------------------
/labs/images/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | Don't worry about installing Java, remember the official [OpenJDK](https://hub.docker.com/_/openjdk) image does that for you. The app is built for Java 8, but it runs fine on newer versions.
4 |
5 | The Dockerfile should be straightforward, but you need to think about the paths. The Java class file may be in a different directory from your Dockerfile, and Docker needs to access both from the context.
6 |
7 | Also the Java command inside the container needs to use the correct path to find the class file you copy into the image.
8 |
9 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/images/java/HelloWorld.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/courselabs/docker/bb2fed68581264e0fc2e344d92ff68e5938da183/labs/images/java/HelloWorld.class
--------------------------------------------------------------------------------
/labs/images/java/HelloWorld.java:
--------------------------------------------------------------------------------
1 | public class HelloWorld {
2 | public static void main(String[] args) {
3 | System.out.println("Hello, World");
4 | }
5 | }
--------------------------------------------------------------------------------
/labs/images/lab/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-jre-alpine
2 | # or :11-jre-slim
3 |
4 | WORKDIR /app
5 | COPY java/HelloWorld.class .
6 |
7 | CMD java HelloWorld
--------------------------------------------------------------------------------
/labs/images/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | Here's a sample solution:
4 |
5 | - [lab/Dockerfile](./lab/Dockerfile)
6 |
7 | It just creates a working directory called `/app` in the container filesystem, and copies in the Java class file.
8 |
9 | The class file and the Dockerfile are in different directories, so you need to use a context where Docker can access both files:
10 |
11 | ```
12 | - labs
13 | |- images <- this is the context
14 | |-- java <- so Docker can get the class file from here
15 | |-- lab <- and the Dockerfile from here
16 | ```
17 | Build the image using that context and specifying the path to the Dockerfile:
18 |
19 | ```
20 | docker build -t java-hello-world -f labs/images/lab/Dockerfile labs/images
21 | ```
22 |
23 | Run a container from the image:
24 |
25 | ```
26 | docker run java-hello-world
27 | ```
28 |
29 | > The output should say `Hello, World`
30 |
31 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/images/web/Dockerfile:
--------------------------------------------------------------------------------
1 | # use a specific version of Nginx:
2 | FROM nginx:1.23.0-alpine
3 |
4 | # overwrite the default HTML doc:
5 | COPY index.html /usr/share/nginx/html/
--------------------------------------------------------------------------------
/labs/images/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Docker Course Labs
5 |
6 |
7 | Docker Course Labs
8 | Image Building Lab
9 | The real content is at docker.courselabs.co
10 |
11 |
--------------------------------------------------------------------------------
/labs/kubernetes/pods/sleep.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: sleep
5 | spec:
6 | containers:
7 | - name: sleep
8 | image: kiamol/ch03-sleep
--------------------------------------------------------------------------------
/labs/kubernetes/pods/whoami.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: whoami
5 | labels:
6 | app: whoami
7 | spec:
8 | containers:
9 | - name: app
10 | image: sixeyed/whoami:21.04
--------------------------------------------------------------------------------
/labs/kubernetes/services/whoami-nodeport.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: whoami-np
5 | spec:
6 | selector:
7 | app: whoami
8 | ports:
9 | - name: http
10 | port: 8080
11 | nodePort: 30000
12 | targetPort: 80
13 | type: NodePort
--------------------------------------------------------------------------------
/labs/multi-stage/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | The [Dockerfile](./whoami/Dockerfile) for the app uses the `ENTRYPOINT` instruction to start the app, so any arguments you pass to the `docker run` command get passed onto the container.
4 |
5 | In the Dockerfile the `EXPOSE` command tells Docker which ports the app expects to listen to. This is built into the image as metadata, it's not linked to the ports the app actually listens on.
6 |
7 | Publishing all ports won't do what you want if the app listens on a different port than the container image expects.
8 |
9 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/multi-stage/simple/Dockerfile:
--------------------------------------------------------------------------------
1 | # pretend to install some packages
2 | FROM alpine:3.13 AS base
3 | RUN echo 'Adding deps...' > /deps.txt
4 |
5 | # pretend build
6 | FROM base AS build
7 | RUN echo 'Building...' > /build.txt
8 |
9 | # pretend test suite
10 | FROM build AS test
11 | COPY --from=build /build.txt /build.txt
12 | RUN echo 'Testing...' >> /build.txt
13 |
14 | # final app image
15 | FROM base
16 | COPY --from=build /build.txt /build.txt
17 | CMD cat /deps.txt && cat /build.txt
--------------------------------------------------------------------------------
/labs/multi-stage/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | Running the app with a custom port just needs you to pass the arguments to the application:
4 |
5 | ```
6 | # run the app listening on port 5000 inside the container:
7 | docker run -d -P --name whoami2 whoami -port 5000
8 | ```
9 |
10 | ## Troubleshooting
11 |
12 | You can check the port the app uses - but if you try to call it you won't get a response:
13 |
14 | ```
15 | docker port whoami2
16 |
17 | curl localhost:
18 | ```
19 |
20 | Check the logs and you'll see why - Docker is directing traffic to port 80, which is the port the image exposes. But the app is not listening on that port:
21 |
22 | ```
23 | docker logs whoami2
24 | ```
25 |
26 | ## The fix
27 |
28 | You need to explicitly map the port if you're configuring the app to use a port which is not exposed in the image:
29 |
30 | ```
31 | docker run -d -p 8050:5000 --name whoami3 whoami -port 5000
32 |
33 | curl localhost:8050
34 | ```
35 |
36 | Or if you want Docker to set a random host port, specify only the target port:
37 |
38 | ```
39 | docker run -d -p :5000 --name whoami4 whoami -port 5000
40 |
41 | docker port whoami4
42 |
43 | curl localhost:
44 | ```
45 |
46 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/multi-stage/whoami/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.16.4-alpine AS builder
2 | ENV CGO_ENABLED=0
3 |
4 | RUN apk --no-cache --no-progress add \
5 | ca-certificates \
6 | git \
7 | tzdata \
8 | && update-ca-certificates
9 |
10 | WORKDIR /go/whoami
11 | COPY go.mod .
12 | RUN go mod download
13 |
14 | COPY app.go .
15 | RUN go build -o /out/whoami
16 |
17 | # app
18 | FROM scratch
19 |
20 | ENTRYPOINT ["/app/whoami"]
21 | EXPOSE 80
22 |
23 | COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
24 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
25 | COPY --from=builder /out/whoami /app/
26 |
--------------------------------------------------------------------------------
/labs/multi-stage/whoami/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/traefik/whoami
2 |
3 | go 1.16
4 |
5 | require github.com/gorilla/websocket v1.4.2
6 |
--------------------------------------------------------------------------------
/labs/networking/compose-network.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Logging__LogLevel__Default=Debug
6 | volumes:
7 | - ./scripts:/scripts
8 | networks:
9 | app-net:
10 | ipv4_address: 10.218.0.10
11 |
12 | rng-web:
13 | image: courselabs/rng-web:21.05
14 | environment:
15 | - Logging__LogLevel__Default=Debug
16 | - RngApi__Url=http://10.218.0.10/rng
17 | ports:
18 | - "8090:80"
19 | networks:
20 | - app-net
21 |
22 | networks:
23 | app-net:
24 | name: courselabs-rng
25 | ipam:
26 | driver: default
27 | config:
28 | - subnet: 10.218.0.0/16
29 |
30 |
--------------------------------------------------------------------------------
/labs/networking/compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | environment:
5 | - Logging__LogLevel__Default=Debug
6 | networks:
7 | - app-net
8 |
9 | rng-web:
10 | image: courselabs/rng-web:21.05
11 | environment:
12 | - Logging__LogLevel__Default=Debug
13 | - RngApi__Url=http://rng-api/rng
14 | ports:
15 | - "8090:80"
16 | networks:
17 | - app-net
18 |
19 | networks:
20 | app-net:
21 |
--------------------------------------------------------------------------------
/labs/networking/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | You can't scale up the most recent deployment, because the Compose spec uses a specific IP address for the API container. Roll back to the [compose.yml](./compose.yml) spec and you can scale up now.
4 |
5 | Follow the logs of the API containers and you should see them all being used when you get lots of random numbers from the website.
6 |
7 | Check the details of all the API containers to see the networking setup Compose applies to get that load-balancing behaviour.
8 |
9 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/networking/scripts/network-info.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo ''
4 |
5 | echo "** Hostname **"
6 | echo $(hostname)
7 | echo ''
8 |
9 | echo "** IP address **"
10 | echo $(hostname -i)
11 | echo ''
12 |
13 | echo "** DNS server: **"
14 | cat /etc/resolv.conf | grep nameserver | cut -c 12-
15 | echo ''
--------------------------------------------------------------------------------
/labs/networking/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | Roll back to the earlier deployment:
4 |
5 | ```
6 | docker-compose -f labs/networking/compose.yml up -d
7 | ```
8 |
9 | And you can scale up with the Compose CLI:
10 |
11 | ```
12 | docker-compose -f labs/networking/compose.yml up -d --scale rng-api=3
13 | ```
14 |
15 | > You'll see two new API containers created.
16 |
17 | Follow the logs from all API containers:
18 |
19 | ```
20 | docker-compose -f labs/networking/compose.yml logs -f rng-api
21 | ```
22 |
23 | > Use the app at http://localhost:8090 and you'll see different API containers generating numbers.
24 |
25 | Inspect the API containers and you'll see compose sets the same network alias for each of them:
26 |
27 | ```
28 | docker inspect networking_rng-api_1
29 | ```
30 |
31 | - includes the container's hostname and the Compose service name in the .NetworkSettings.Networks section, e.g.
32 |
33 | ```
34 | "Aliases": [
35 | "rng-api",
36 | "b382ce0e8ffc"
37 | ]
38 | ```
39 |
40 | Any containers with the same alias will get returned in the DNS response for that domain name.
41 |
42 |
43 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/orchestration/config/api.json:
--------------------------------------------------------------------------------
1 | {
2 | "Rng" : {
3 | "Range": {
4 | "Min": 0,
5 | "Max": 50000
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/labs/orchestration/config/logging.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging" : {
3 | "LogLevel" : {
4 | "Default" : "Information"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/labs/orchestration/config/web.json:
--------------------------------------------------------------------------------
1 | {
2 | "RngApi" : {
3 | "Url" : "http://rng-api/rng"
4 | }
5 | }
--------------------------------------------------------------------------------
/labs/orchestration/rng-v1.yml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 |
3 | services:
4 | rng-api:
5 | image: courselabs/rng-api:21.05
6 | configs:
7 | - source: rng-logging
8 | target: /app/config/logging.json
9 | secrets:
10 | - source: rng-api
11 | target: /app/config/override.json
12 | ports:
13 | - "8089:80"
14 | networks:
15 | - app-net
16 |
17 | rng-web:
18 | image: courselabs/rng-web:21.05
19 | configs:
20 | - source: rng-logging
21 | target: /app/config/logging.json
22 | secrets:
23 | - source: rng-web
24 | target: /app/config/override.json
25 | ports:
26 | - "8090:80"
27 | networks:
28 | - app-net
29 |
30 | networks:
31 | app-net:
32 |
33 | configs:
34 | rng-logging:
35 | external: true
36 |
37 | secrets:
38 | rng-api:
39 | external: true
40 |
41 | rng-web:
42 | external: true
43 |
--------------------------------------------------------------------------------
/labs/orchestration/rng-v2.yml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 |
3 | services:
4 | rng-api:
5 | image: courselabs/rng-api:21.05
6 | environment:
7 | - Logging__LogLevel__Default=Debug
8 | - Rng__FailAfter__Enabled=true
9 | - Rng__FailAfter__CallCount=10
10 | - Rng__FailAfter__Action=Exit
11 | configs:
12 | - source: rng-logging
13 | target: /app/config/logging.json
14 | secrets:
15 | - source: rng-api
16 | target: /app/config/override.json
17 | ports:
18 | - "8089:80"
19 | networks:
20 | - app-net
21 | deploy:
22 | replicas: 4
23 |
24 | rng-web:
25 | image: courselabs/rng-web:21.05
26 | configs:
27 | - source: rng-logging
28 | target: /app/config/logging.json
29 | secrets:
30 | - source: rng-web
31 | target: /app/config/override.json
32 | ports:
33 | - "8090:80"
34 | networks:
35 | - app-net
36 | deploy:
37 | replicas: 2
38 |
39 | networks:
40 | app-net:
41 |
42 | configs:
43 | rng-logging:
44 | external: true
45 |
46 | secrets:
47 | rng-api:
48 | external: true
49 |
50 | rng-web:
51 | external: true
52 |
--------------------------------------------------------------------------------
/labs/registries/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | The default tag is shown when you list images, but the default registry isn't.
4 |
5 | You can print the `info` about your Docker engine to get a hint on the default registry domain.
6 |
7 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/registries/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | The default registry is Docker Hub - using the domain `docker.io`, and the default image tag is `latest`.
4 |
5 | So `kiamol/ch05-pi` is the short form of `docker.io/kiamol/ch05-pi:latest`:
6 |
7 | ```
8 | docker pull kiamol/ch05-pi
9 |
10 | docker pull docker.io/kiamol/ch05-pi:latest
11 | ```
12 |
13 | Check the image list and you'll only see one - these aren't aliases, they're just different froms of the same name:
14 |
15 | ```
16 | docker image ls kiamol/ch05-pi
17 | ```
18 |
19 | > Be wary of using `latest` images - it's a confusing name because it might not be the latest version.
20 |
21 | For other registries you need to include the domain in the reference - so images on MCR need to be prefixed with `mcr.microsoft.com/`:
22 |
23 | ```
24 | docker pull mcr.microsoft.com/dotnet/runtime:6.0
25 | ```
26 |
27 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/labs/troubleshooting/.env:
--------------------------------------------------------------------------------
1 | COMPOSE_PROJECT_NAME=troubleshooting
2 |
--------------------------------------------------------------------------------
/labs/troubleshooting/README.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting Containerized Apps
2 |
3 | You'll spend a lot of your time with the Docker CLI and your Compose YAML files troubleshooting problems.
4 |
5 | The CLIs validate commands and specs for correctness when you deploy them, but they don't check that your app will actually work.
6 |
7 | Images can be misconfigured, the container environment could be incorrect, and components might be unable to reach each other.
8 |
9 | ## Lab
10 |
11 | This one is all lab :) Try running this app - and make whatever changes you need to get the app running, so the containers run and the app works.
12 |
13 | ```
14 | docker-compose -f labs/troubleshooting/compose.yml up -d
15 | ```
16 |
17 | > Your goal is to browse to http://localhost:8090 and have a working random number generator.
18 |
19 | Don't go straight to the solution! These are the sort of issues you will get all the time, so it's good to start working through the steps to diagnose problems.
20 |
21 | > Stuck? Try [hints](hints.md) or check the [solution](solution.md).
22 |
23 | ___
24 | ## Cleanup
25 |
26 | When you're done you can remove all the objects:
27 |
28 | ```
29 | docker-compose -f labs/troubleshooting/compose.yml down
30 | ```
--------------------------------------------------------------------------------
/labs/troubleshooting/compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | entrypoint: ["dontet", "/app/Numbers.Api.dll"]
5 | env_file: ./config/logging.env
6 | ports:
7 | - "8089:80"
8 | volumes:
9 | - ./config/api:/app
10 | networks:
11 | - app-net
12 |
13 | rng-web:
14 | image: courselabs/rng-website:21.05
15 | env_file: ./config/logging.env
16 | environment:
17 | - RngApi__Url=http://rng-api/rng
18 | ports:
19 | - "8089:80"
20 | cpus: 25
21 | networks:
22 | - front-end
23 |
24 | networks:
25 | app-net:
26 | front-end:
27 |
--------------------------------------------------------------------------------
/labs/troubleshooting/config/api/override.json:
--------------------------------------------------------------------------------
1 | {
2 | "Rng" : {
3 | "Range": {
4 | "Min": 0,
5 | "Max": 50
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/labs/troubleshooting/config/logging.env:
--------------------------------------------------------------------------------
1 | Logging__LogLevel__Default=Debug
--------------------------------------------------------------------------------
/labs/troubleshooting/hints.md:
--------------------------------------------------------------------------------
1 | # Lab Hints
2 |
3 | Fixing apps is a process of checking the status of running objects and seeing what's wrong, then fixing up the YAML and redeploying.
4 |
5 | You'll find different classes of problem with this app:
6 |
7 | - validation failures which stop containers being created
8 | - startup failures which mean containers can't run
9 | - runtime failures where the container is running but the app doesn't work
10 | - networking failures where containers can't reach each other
11 |
12 | Use the normal command lines to deploy and check the output carefully.
13 |
14 | Once the containers are running you can print the logs and run commands inside the containers to check the filesystem.
15 |
16 | > Need more? Here's the [solution](solution.md).
--------------------------------------------------------------------------------
/labs/troubleshooting/lab/solution.yml:
--------------------------------------------------------------------------------
1 | services:
2 | rng-api:
3 | image: courselabs/rng-api:21.05
4 | entrypoint: ["dotnet", "/app/Numbers.Api.dll"]
5 | env_file: ./config/logging.env
6 | ports:
7 | - "8089:80"
8 | volumes:
9 | - ./config/api:/app/config
10 | networks:
11 | - app-net
12 |
13 | rng-web:
14 | image: courselabs/rng-web:21.05
15 | env_file: ./config/logging.env
16 | environment:
17 | - RngApi__Url=http://rng-api/rng
18 | ports:
19 | - "8090:80"
20 | cpus: 2.5
21 | networks:
22 | - app-net
23 | - front-end
24 |
25 | networks:
26 | app-net:
27 | front-end:
28 |
--------------------------------------------------------------------------------
/labs/troubleshooting/solution.md:
--------------------------------------------------------------------------------
1 | # Lab Solution
2 |
3 | My solution is here:
4 |
5 | - [solution.yml](./lab/solution.yml)
6 |
7 | Copy it to the main folder so the config paths are correct, and the app will work:
8 |
9 | ```
10 | cp labs/troubleshooting/lab/solution.yml labs/troubleshooting/
11 |
12 | docker-compose -f labs/troubleshooting/solution.yml up -d
13 | ```
14 |
15 | > Use the app at http://localhost:8090
16 |
17 | ## Validation failures
18 |
19 | 1. The image name is incorrect for the web component. You'll see a `manifest unknown` error.
20 |
21 | 2. Not enough CPUs - the web application request `25` CPUs, which is a typo - it should be `2.5`. Unless you have a mega powerful machine, Docker can't allocate enough CPUs to create the container.
22 |
23 | ## Startup failures
24 |
25 | 1. Incorrect entrypoint for the API. It's specifying the command to run but `dontet` is a typo. You'll see `executable file not found in $PATH` - it should be `dotnet`.
26 |
27 | 2. Duplicate port mapping - both the web app and API are trying to publish port `8089`. You'll see `port is already allocated` - the web app should be publishing to port `8090`.
28 |
29 | ## Runtime failures
30 |
31 | 1. The API container exits after starting. Check the logs and you'll see `the application was not found`. The volume mount is incorrect - it's loading the config folder into the `/app` folder, which overwrites the contents from the image so there is no application binary. The volume target should be `/app/config`.
32 |
33 | ## Networking failures
34 |
35 | 1. Try using the website and you'll get the `RNG service unavailable!` error. The web container logs show you the app is using the correct URL, but if you run `nslookup` the API container can't be found. The spec uses two different networks, so the containers aren't connected - the web container needs to attach to the `app-net` network.
36 |
37 |
38 | > Back to the [exercises](README.md).
--------------------------------------------------------------------------------
/setup/README.md:
--------------------------------------------------------------------------------
1 | # Set Up Docker and Git
2 |
3 | Docker runs as a background service on server operating systems, but in a local environment the easiest option is Docker Desktop.
4 |
5 | We'll also use [Git](https://git-scm.com) for source control, so you'll need a client on your machine to talk to GitHub.
6 |
7 | ## Git Client - Mac, Windows or Linux
8 |
9 | Git is a free, open source tool for source control:
10 |
11 | - [Install Git](https://git-scm.com/downloads)
12 |
13 | ## Docker Desktop - Mac or Windows
14 |
15 | If you're on macOS or Windows 10, Docker Desktop is for you:
16 |
17 | - [Install Docker Desktop](https://www.docker.com/products/docker-desktop)
18 |
19 | The download and install takes a few minutes. When it's done, run the _Docker_ app and you'll see the Docker whale logo in your taskbar (Windows) or menu bar (macOS).
20 |
21 | > On Windows 10 the install may need a restart before you get here.
22 |
23 | ## **OR** Docker Engine - Linux
24 |
25 |
26 | Running Docker on Linux
27 |
28 | Docker Engine is the background service which runs containers. You can install it - along with the Docker command line - for lots of different Linux distros:
29 |
30 | - [Install Docker Engine](https://docs.docker.com/engine/install/)
31 | - [Install Docker Compose](https://docs.docker.com/compose/install/)
32 |
33 | > If you're using WSL on Windows 10, it's much easier to use Docker Desktop which integrates with your WSL distro.
34 |
35 |
36 |
37 | ## Check your setup
38 |
39 | When you have Git and Docker installed you should be able to run these commands and get some output:
40 |
41 | ```
42 | git --version
43 | ```
44 |
45 | I'm using Git for Windows and my output is:
46 |
47 | ```
48 | git version 2.31.1.windows.1
49 | ```
50 |
51 | Then run:
52 |
53 | ```
54 | docker version
55 | ```
56 |
57 | I'm using Docker Desktop on Windows and mine says:
58 |
59 | ```
60 | Client:
61 | Cloud integration: 1.0.14
62 | Version: 20.10.6
63 | API version: 1.41
64 | Go version: go1.16.3
65 | Git commit: 370c289
66 | Built: Fri Apr 9 22:49:36 2021
67 | OS/Arch: windows/amd64
68 | Context: default
69 | Experimental: true
70 |
71 | Server: Docker Engine - Community
72 | Engine:
73 | Version: 20.10.6
74 | API version: 1.41 (minimum version 1.12)
75 | Go version: go1.13.15
76 | Git commit: 8728dd2
77 | Built: Fri Apr 9 22:44:56 2021
78 | OS/Arch: linux/amd64
79 | ...
80 | ```
81 |
82 | And then:
83 |
84 | ```
85 | docker-compose version
86 | ```
87 |
88 | My output is:
89 |
90 | ```
91 | docker-compose version 1.29.1, build c34c88b2
92 | docker-py version: 5.0.0
93 | CPython version: 3.9.0
94 | OpenSSL version: OpenSSL 1.1.1g 21 Apr 2020
95 | ```
96 |
97 | > Your details and version numbers may be different - that's fine. If you get errors then we need to look into it, because you'll need to have Docker running for all of the exercises.
98 |
99 | > ❗ If you're running Docker Desktop on Windows, make sure you're in _Linux container mode_. This is the default mode, but if you've changed to using Windows containers (from the whale toolbar menu), then you'll need to switch back.
--------------------------------------------------------------------------------