diff --git a/.devcontainer/.vscode-server/.gitkeep b/.devcontainer/.vscode-server/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..f22718318525e0a372ff92264a4f731beb9d658b --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,5 @@ +# Development container for dotnet + +FROM mcr.microsoft.com/devcontainers/dotnet:8.0 as develop + +USER vscode \ No newline at end of file diff --git a/.devcontainer/compose.yml b/.devcontainer/compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..50384ae06f8e2a50e7101eddd1c4adf35cd2a88a --- /dev/null +++ b/.devcontainer/compose.yml @@ -0,0 +1,22 @@ +version: "3.9" + +services: + + app: + build: + target: develop + user: vscode + userns_mode: keep-id:uid=1000,gid=1000 + environment: + SSH_AUTH_SOCK: /.ssh/ssh-agent.sock + command: /bin/sh -c "while sleep 2s; do :; done" + volumes: + - ..:/workspace/graph-deployer:cached + - ./.vscode-server:/home/vscode/.vscode-server/:cached + - ${SSH_AUTH_SOCK}:/.ssh/ssh-agent.sock + networks: + - default + +networks: + default: + driver: bridge diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000000000000000000000000000000000..46ae9f2cddc85ca04fb315c81c6636e72897dd6b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Graph Deployer", + "dockerComposeFile": [ + "compose.yml" + ], + "service": "app", + "workspaceFolder": "/workspace/graph-deployer", + "updateRemoteUserUID": false, + "remoteUser": "vscode", + "containerUser": "vscode", + "customizations": { + "vscode": { + "settings": {}, + "extensions": [ + "ms-dotnettools.csdevkit", + "ms-azuretools.vscode-docker", + "ms-dotnettools.csharp", + "mhutchie.git-graph", + "mutantdino.resourcemonitor" + ] + } + }, + "forwardPorts": [] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7cdf465cdfbcae98eaca0ef127878a9b08936ec9..0103a271330530a16c89b491897294e934f7d602 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,18 @@ +/.devcontainer/.vscode-server/* +!/.devcontainer/.vscode-server/.gitkeep + +/.devcontainer/.data/* +!/.devcontainer/.data/.gitkeep + +.logs/ +Output/* +internallog.txt + +## Ignore the secrets settings +appsettings.Development.json +appsettings.Staging.json +appsettings.Production.json + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f42ae1c27ea20c0063524c1eb5f96693162dbb2c..08533ce34d20930fb3d3efd13200da911718b35c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ include: - project: coscine/tools/gitlab-ci-templates file: - /dotnet.yml + - /container.yml stages: - build @@ -21,3 +22,8 @@ publish-gitlab-release: publish: extends: .publish-artifact-release + +publish-container: + variables: + CONTAINER_CONTEXT_FOLDER_PATH: $CI_PROJECT_DIR/src/$DOTNET_MAIN_PROJECT_FOLDER + extends: .publish-container-gitlab diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000000000000000000000000000000000..8d09799fe7a6c17af19a06b2ba3e4a249fe1c9b5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": + [ + { + "name": "C#: GraphDeployer Debug", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "dotnet: build", + "program": "${workspaceFolder}/src/GraphDeployer/bin/Debug/net8.0/Coscine.GraphDeployer.dll", + "args": [], + "cwd": "${workspaceFolder}/src/GraphDeployer", + "stopAtEntry": false, + "console": "internalConsole", + "env": { + "DOTNET_ENVIRONMENT": "Development" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..bcf184968422b23b83ba23724646274adaa2faf2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "resmon.disk.format": "UsedOutOfTotal", + "resmon.show.battery": false, + "resmon.show.cpufreq": false, + "resmon.show.disk": true, + "resmon.disk.drives": null +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..85beec3e7a8f83568d729a0d76f4b6ba0a05dd93 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "dotnet", + "task": "build", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [], + "label": "dotnet: build" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 149ff1458143061b8cf18a8e67d8b3c663e172ec..fcea2a4b6f7fcba29e5045f234fb3055d87e5f06 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,25 @@ The Graph Deployer, intended to be used as a CRON job, is a .NET application designed to manage and deploy RDF graphs. It interacts with configured GitLab repositories to pull RDF graph definitions and updates a centralized repository if changes are detected or redeployment is triggered. The job uses a series of configurations to determine the operational parameters including the execution environment, repository access, and deployment specifications. +## Getting Started + +Clone the repository: + +```bash +$ mkdir -p ~/code/git.rwth-aachen.de/coscine/backend/scripts && cd "$_" +$ git clone git@git.rwth-aachen.de:coscine/backend/scripts/graphdeployer.git +``` + +Open the new folder in code and in a [devcontainer](https://code.visualstudio.com/docs/devcontainers/containers#_open-a-folder-on-a-remote-ssh-host-in-a-container). + +Insider the devcontainer, create a development settings file: + +```bash +$ cp /workspace/graph-deployer/src/GraphDeployer/appsettings.Example.json /workspace/graph-deployer/src/GraphDeployer/appsettings.Development.json +``` + +In the `appsettings.Development.json` Replace any `null` value with a fitting value. + ### Features - **Configuration-driven**: Behavior driven by settings defined in configuration files and environment variables (see `appsettings.json`). - **Supports Dummy Mode**: Can run in a non-operative mode for testing configurations and process flow without affecting live data (CLI arg `--dummy`). diff --git a/src/GraphDeployer/Deployer.cs b/src/GraphDeployer/Deployer.cs index 3895d42ac6a7dbc1527a3180509b98309e41743e..7349f513ae53259201212b7b5b096f2d900c5352 100644 --- a/src/GraphDeployer/Deployer.cs +++ b/src/GraphDeployer/Deployer.cs @@ -13,19 +13,24 @@ using Configuration = Coscine.ApiClient.Core.Client.Configuration; namespace Coscine.GraphDeployer; -public class Deployer(ILogger<Deployer> logger, IOptionsMonitor<GraphDeployerConfiguration> graphDeployerConfiguration) +public class Deployer { - private readonly ILogger<Deployer> _logger = logger; - private readonly GraphDeployerConfiguration _graphDeployerConfiguration = graphDeployerConfiguration.CurrentValue; - private readonly AdminApi _adminApi = new(_configuration); + private readonly ILogger<Deployer> _logger; + private readonly GraphDeployerConfiguration _graphDeployerConfiguration; + private readonly AdminApi _adminApi; - private static readonly Configuration _configuration = new() + public Deployer(ILogger<Deployer> logger, IOptionsMonitor<GraphDeployerConfiguration> graphDeployerConfiguration) { - BasePath = "http://localhost:7206/coscine", - ApiKeyPrefix = { { "Authorization", "Bearer" } }, - ApiKey = { { "Authorization", ApiConfigurationUtil.GenerateAdminToken(ApiConfigurationUtil.RetrieveJwtConfiguration()) } }, - Timeout = 300000 // 5 minutes - }; + _logger = logger; + _graphDeployerConfiguration = graphDeployerConfiguration.CurrentValue; + _adminApi = new(new Configuration + { + BasePath = $"{_graphDeployerConfiguration.Endpoint.TrimEnd('/')}/coscine", + ApiKeyPrefix = { { "Authorization", "Bearer" } }, + ApiKey = { { "Authorization", _graphDeployerConfiguration.ApiKey } }, + Timeout = 300000 // 5 minutes + }); + } public static string WorkingFolder { get; set; } = "./output/"; public static List<string> DeployedGraphs { get; set; } = []; diff --git a/src/GraphDeployer/Dockerfile b/src/GraphDeployer/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..d8e70b7de68547fefb86793c09e9a5d24165f612 --- /dev/null +++ b/src/GraphDeployer/Dockerfile @@ -0,0 +1,39 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env +WORKDIR /App + +# Copy everything +COPY . ./ + +# Restore as distinct layers +RUN dotnet restore + +# Build and publish a release +RUN dotnet publish -c Release -o out + +# Build runtime image +FROM mcr.microsoft.com/dotnet/aspnet:8.0 + +# Install cron and other necessary packages +RUN apt-get update && \ + apt-get install -y cron && \ + apt-get clean + +WORKDIR /App +COPY --from=build-env /App/out . + +# Copy the cron job file to /etc/cron.d/ +COPY graph-deployer-cron /etc/cron.d/ + +# Give execution rights on the cron job file +RUN chmod 0644 /etc/cron.d/graph-deployer-cron + +# Apply the cron job +RUN crontab /etc/cron.d/graph-deployer-cron + +# Set the build-time argument and default environment variable +ENV DOTNET_ENVIRONMENT=Development + +# Create the log file +RUN touch /var/log/cron.log + +ENTRYPOINT /bin/sh -c "cron;/App/Coscine.GraphDeployer;tail -f /var/log/cron.log" diff --git a/src/GraphDeployer/GraphDeployer.csproj b/src/GraphDeployer/GraphDeployer.csproj index 16946bd18ff886d53435c912237150a757ddd4de..656adca43357136cd798e32ed7fdad103d62f340 100644 --- a/src/GraphDeployer/GraphDeployer.csproj +++ b/src/GraphDeployer/GraphDeployer.csproj @@ -22,7 +22,7 @@ <ItemGroup> <PackageReference Include="CommandLineParser" Version="2.9.1" /> - <PackageReference Include="Coscine.ApiClient" Version="1.7.0-issue-2668-graph0001" /> + <PackageReference Include="Coscine.ApiClient" Version="1.8.0" /> <PackageReference Include="dotNetRdf" Version="3.1.1" /> <PackageReference Include="GitLabApiClient" Version="1.8.1-beta.5" /> <PackageReference Include="LibGit2Sharp" Version="0.30.0" /> @@ -40,6 +40,9 @@ <None Update="appsettings.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> + <None Update="appsettings.development.json"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> <None Update="nlog.config"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> diff --git a/src/GraphDeployer/Models/ConfigurationModels/GraphDeployerConfiguration.cs b/src/GraphDeployer/Models/ConfigurationModels/GraphDeployerConfiguration.cs index cf2e346374b4af78958f96d70fbdbd7ecf711920..7810fd211c2225fb68ffa6cb3dbcca7a95af4c54 100644 --- a/src/GraphDeployer/Models/ConfigurationModels/GraphDeployerConfiguration.cs +++ b/src/GraphDeployer/Models/ConfigurationModels/GraphDeployerConfiguration.cs @@ -17,6 +17,17 @@ public class GraphDeployerConfiguration /// </summary> public string? WorkingFolder { get; init; } + /// <summary> + /// API token for the coscine API. + /// Requires the administrator role. + /// </summary> + public string ApiKey { get; set; } = null!; + + /// <summary> + /// Address of the Coscine API. + /// </summary> + public string Endpoint { get; set; } = null!; + /// <summary> /// Logger configuration settings. /// </summary> diff --git a/src/GraphDeployer/Program.cs b/src/GraphDeployer/Program.cs index c60e8b39113115cb922adf3ec808a443c45cdbf0..3b45eeab4a38c1de93bfdf5db7815c204ddf7924 100644 --- a/src/GraphDeployer/Program.cs +++ b/src/GraphDeployer/Program.cs @@ -15,8 +15,14 @@ public class Program { private static IServiceProvider _serviceProvider = null!; + private static string? _environmentName; + public static int Main(string[] args) { + _environmentName = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); + + Console.WriteLine($"Running in environment: {_environmentName ?? "environment is null"}"); + InitializeServices(); var logger = _serviceProvider.GetRequiredService<ILogger<Program>>(); @@ -65,7 +71,8 @@ public class Program // Add Consul as a configuration source var configuration = configBuilder - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{_environmentName}.json", optional: true, reloadOnChange: true) .AddConsul( "coscine/Coscine.Infrastructure/GraphDeployer/appsettings", options => @@ -78,6 +85,18 @@ public class Program options.OnLoadException = exceptionContext => exceptionContext.Ignore = true; } ) + .AddConsul( + $"coscine/Coscine.Infrastructure/GraphDeployer/appsettings.{_environmentName}.json", + options => + { + options.ConsulConfigurationOptions = + cco => cco.Address = new Uri(consulUrl); + options.Optional = true; + options.ReloadOnChange = true; + options.PollWaitTime = TimeSpan.FromSeconds(5); + options.OnLoadException = exceptionContext => exceptionContext.Ignore = true; + } + ) .AddEnvironmentVariables() .Build(); @@ -100,6 +119,17 @@ public class Program var graphDeployerConfiguration = new GraphDeployerConfiguration(); configuration.Bind(GraphDeployerConfiguration.Section, graphDeployerConfiguration); + + if (graphDeployerConfiguration.Endpoint is null) + { + throw new ArgumentNullException(nameof(graphDeployerConfiguration.Endpoint), "Endpoint cannot be null."); + } + + if (graphDeployerConfiguration.ApiKey is null) + { + throw new ArgumentNullException(nameof(graphDeployerConfiguration.ApiKey), "ApiKey cannot be null."); + } + // Set the default LogLevel LogManager.Configuration.Variables["logLevel"] = graphDeployerConfiguration.Logger?.LogLevel ?? "Trace"; // Set the log location diff --git a/src/GraphDeployer/appsettings.Example.json b/src/GraphDeployer/appsettings.Example.json new file mode 100644 index 0000000000000000000000000000000000000000..302c1b3f4bc76d276422a03ab569a77b5ff7c38c --- /dev/null +++ b/src/GraphDeployer/appsettings.Example.json @@ -0,0 +1,9 @@ +{ + "GraphDeployerConfiguration": { + "ApiKey": null, + "Endpoint": null, + "GitLab": { + "Token": null + } + } +} \ No newline at end of file diff --git a/src/GraphDeployer/appsettings.json b/src/GraphDeployer/appsettings.json index 836defeaa1554902878f9cc20efac9728a18bff6..6fa53903c346bdc5e3ebef4439dba0c1f05054fc 100644 --- a/src/GraphDeployer/appsettings.json +++ b/src/GraphDeployer/appsettings.json @@ -1,24 +1,24 @@ { "GraphDeployerConfiguration": { + "ApiKey": null, + "Endpoint": null, "IsEnabled": true, - "WorkingFolder": "C:/coscine/GraphDeployer/output/", + "WorkingFolder": "./Output/", "Logger": { "LogLevel": "Information", - "LogHome": "C:/coscine/GraphDeployer/" + "LogHome": "./Logs" }, "GitLab": { "HostUrl": "https://git.rwth-aachen.de/", - "Token": "glpat-iXFFtZSjmbhn8U4-YFzv", + "Token": null, "Repositories": [ { "Name": "Application Profiles", - "Url": "https://git.rwth-aachen.de/coscine/graphs/applicationprofiles.git", - "Branch": "master" + "Url": "https://git.rwth-aachen.de/coscine/graphs/applicationprofiles.git" }, { "Name": "Organisations", - "Url": "https://git.rwth-aachen.de/coscine/graphs/organizations.git", - "Branch": "master" + "Url": "https://git.rwth-aachen.de/coscine/graphs/organizations.git" }, { "Name": "FH Bielefeld", diff --git a/src/GraphDeployer/graph-deployer-cron b/src/GraphDeployer/graph-deployer-cron new file mode 100644 index 0000000000000000000000000000000000000000..9b333af7115505ba388f9946e0e2a4bb17b02154 --- /dev/null +++ b/src/GraphDeployer/graph-deployer-cron @@ -0,0 +1 @@ +* 3 * * * root /App/Coscine.GraphDeployer >> /var/log/cron.log 2>&1 diff --git a/src/GraphDeployer/nlog.config b/src/GraphDeployer/nlog.config index c2fd7e323b320dadeb7946287dedab42a29a9c82..e57a1713090266ca649ee185a60362ec07956514 100644 --- a/src/GraphDeployer/nlog.config +++ b/src/GraphDeployer/nlog.config @@ -3,35 +3,27 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Warn" - internalLogFile=".\internal_logs\internallog.txt"> + internalLogFile="./internal_logs/internallog.txt"> <variable name="logHome" value="${basedir}/Logs" /> <variable name="logLevel" value="Warn" /> <!--Possible aspnet- variables: https://nlog-project.org/config/?tab=layout-renderers&search=package:nlog.web--> - <variable name="layout" value="${longdate} | [${level:uppercase=true}] ${message} ${exception:format=tostring}" /> - - <extensions> - <!--Enable NLog.Web for ASP.NET Core.--> - <add assembly="NLog.Web.AspNetCore" /> - </extensions> + <variable name="layout" value="${longdate} | [${level:uppercase=true}] ${message} ${exception:format=tostring}" /> <targets> <!-- Write logs to File --> - <target xsi:type="FallbackGroup" - name="fileGroup"> - <target - name="fullfile" - xsi:type="File" - layout="${var:layout}" - fileName="${var:logHome}/full_${shortdate}.log" - keepFileOpen="false" - archiveFileName="${var:logHome}/Archive/full_${shortdate}.log" - archiveNumbering="Sequence" - archiveEvery="Day" - maxArchiveFiles="7" - /> - </target> + <target + name="fullfile" + xsi:type="File" + layout="${var:layout}" + fileName="${var:logHome}/full_${shortdate}.log" + keepFileOpen="false" + archiveFileName="${var:logHome}/Archive/full_${shortdate}.log" + archiveNumbering="Sequence" + archiveEvery="Day" + maxArchiveFiles="7" + /> <!-- Write colored logs to Console --> <target name="consoleLog" xsi:type="ColoredConsole" layout="[${uppercase:${level}}]: ${message}">