Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgraded .dotnet, added Makefile, Dockerfile etc. etc. #159

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ output/*
AzureDevOps.WikiPDFExport.Test/IntegrationTests-Data/Outputs
AzureDevOps.WikiPDFExport.Test/test-data/Inputs/Azure-Platform-Design/*
AzureDevOps.WikiPDFExport.Test/test-data/Inputs/1k/html.html
**output/
**.idea/
export.pdf
197 changes: 91 additions & 106 deletions AzureDevOps.WikiPDFExport/PDFGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,68 +1,66 @@
using Microsoft.Extensions.Logging;
using PuppeteerSharp;
using System.IO;
using System.IO;
using System.Threading.Tasks;
using PuppeteerSharp;
using PuppeteerSharp.Media;

namespace azuredevops_export_wiki;

namespace azuredevops_export_wiki
internal class PDFGenerator
{
internal class PDFGenerator
{
const int MAX_PAGE_SIZE = 100_000_000;
private ILogger _logger;
private Options _options;
private const int MAX_PAGE_SIZE = 100_000_000;
private readonly ILogger _logger;
private readonly Options _options;

internal PDFGenerator(Options options, ILogger logger)
{
_options = options;
_logger = logger;
}
internal PDFGenerator(Options options, ILogger logger)
{
_options = options;
_logger = logger;
}

#if HTML_IN_MEMORY
internal async Task<string> ConvertHTMLToPDFAsync(string html)
#else
internal async Task<string> ConvertHTMLToPDFAsync(SelfDeletingTemporaryFile tempHtmlFile)
internal async Task<string> ConvertHTMLToPDFAsync(SelfDeletingTemporaryFile tempHtmlFile)
#endif
{
_logger.Log("Converting HTML to PDF");
var output = _options.Output;
{
_logger.Log("Converting HTML to PDF");
var output = _options.Output;

if (string.IsNullOrEmpty(output))
{
output = Path.Combine(Directory.GetCurrentDirectory(), "export.pdf");
}
if (string.IsNullOrEmpty(output)) output = Path.Combine(Directory.GetCurrentDirectory(), "export.pdf");

string chromePath = _options.ChromeExecutablePath;
var chromePath = _options.ChromeExecutablePath;

if (string.IsNullOrEmpty(chromePath))
{
string tempFolder = Path.Join(Path.GetTempPath(), "AzureDevOpsWikiExporter");
if (string.IsNullOrEmpty(chromePath))
{
var tempFolder = Path.Join(Path.GetTempPath(), "AzureDevOpsWikiExporter");

_logger.Log("No Chrome path defined, downloading to user temp...");
_logger.Log("No Chrome path defined, downloading to user temp...");

var fetcherOptions = new BrowserFetcherOptions
{
Path = tempFolder,
};
var fetcherOptions = new BrowserFetcherOptions
{
Path = tempFolder
};

var info = await new BrowserFetcher(fetcherOptions).DownloadAsync(BrowserFetcher.DefaultChromiumRevision);
chromePath = info.ExecutablePath;
var info = await new BrowserFetcher(fetcherOptions).DownloadAsync();

_logger.Log("Chrome ready.");
}
chromePath = info.GetExecutablePath();

var launchOptions = new LaunchOptions
{
ExecutablePath = chromePath,
Headless = true, //set to false for easier debugging
Args = new[] { "--no-sandbox", "--single-process" }, //required to launch in linux
Devtools = false,
Timeout = _options.ChromeTimeout * 1000
};
_logger.Log("Chrome ready.");
}

// TODO add logging to Puppeteer
using (var browser = await Puppeteer.LaunchAsync(launchOptions))
{
var page = await browser.NewPageAsync();
var launchOptions = new LaunchOptions
{
ExecutablePath = chromePath,
Headless = true, //set to false for easier debugging
Args = new[] { "--no-sandbox", "--single-process" }, //required to launch in linux
Devtools = false,
Timeout = _options.ChromeTimeout * 1000
};

// TODO add logging to Puppeteer
using (var browser = await Puppeteer.LaunchAsync(launchOptions))
{
var page = await browser.NewPageAsync();
#if HTML_IN_MEMORY
_logger.Log($"Sending {html.Length:N0} bytes to Chrome...");
if (html.Length > MAX_PAGE_SIZE)
Expand All @@ -72,78 +70,65 @@ internal async Task<string> ConvertHTMLToPDFAsync(SelfDeletingTemporaryFile temp
await page.SetContentAsync(html);
_logger.Log($"HTML page filled.");
#else
var f = new FileInfo(tempHtmlFile.FilePath);
_logger.Log($"Asking Chrome to open {f.Length:N0} bytes page at {tempHtmlFile.FilePath}...");
if (f.Length > MAX_PAGE_SIZE)
{
_logger.Log($"This may take a few minutes, given the file size.");
}
await page.GoToAsync($"file://{tempHtmlFile.FilePath}", launchOptions.Timeout);
_logger.Log($"HTML file loaded.");
var f = new FileInfo(tempHtmlFile.FilePath);
_logger.Log($"Asking Chrome to open {f.Length:N0} bytes page at {tempHtmlFile.FilePath}...");
if (f.Length > MAX_PAGE_SIZE) _logger.Log("This may take a few minutes, given the file size.");
await page.GoToAsync($"file://{tempHtmlFile.FilePath}", launchOptions.Timeout);
_logger.Log("HTML file loaded.");
#endif

//todo load header/footer template from file
var pdfoptions = new PdfOptions();
if (!string.IsNullOrEmpty(_options.HeaderTemplate)
|| !string.IsNullOrEmpty(_options.FooterTemplate)
|| !string.IsNullOrEmpty(_options.HeaderTemplatePath)
|| !string.IsNullOrEmpty(_options.FooterTemplatePath))
//todo load header/footer template from file
var pdfoptions = new PdfOptions();
if (!string.IsNullOrEmpty(_options.HeaderTemplate)
|| !string.IsNullOrEmpty(_options.FooterTemplate)
|| !string.IsNullOrEmpty(_options.HeaderTemplatePath)
|| !string.IsNullOrEmpty(_options.FooterTemplatePath))
{
string footerTemplate = null;
string headerTemplate = null;
if (!string.IsNullOrEmpty(_options.HeaderTemplate))
headerTemplate = _options.HeaderTemplate;
else if (!string.IsNullOrEmpty(_options.HeaderTemplatePath))
headerTemplate = File.ReadAllText(_options.HeaderTemplatePath);

if (!string.IsNullOrEmpty(_options.FooterTemplate))
footerTemplate = _options.FooterTemplate;
else if (!string.IsNullOrEmpty(_options.FooterTemplatePath))
footerTemplate = File.ReadAllText(_options.FooterTemplatePath);

pdfoptions = new PdfOptions
{

string footerTemplate = null;
string headerTemplate = null;
if (!string.IsNullOrEmpty(_options.HeaderTemplate))
{
headerTemplate = _options.HeaderTemplate;
}
else if (!string.IsNullOrEmpty(_options.HeaderTemplatePath))
{
headerTemplate = File.ReadAllText(_options.HeaderTemplatePath);
}

if (!string.IsNullOrEmpty(_options.FooterTemplate))
{
footerTemplate = _options.FooterTemplate;
}
else if (!string.IsNullOrEmpty(_options.FooterTemplatePath))
{
footerTemplate = File.ReadAllText(_options.FooterTemplatePath);
}

pdfoptions = new PdfOptions()
PrintBackground = true,
PreferCSSPageSize = false,
DisplayHeaderFooter = true,
MarginOptions =
{
PrintBackground = true,
PreferCSSPageSize = false,
DisplayHeaderFooter = true,
MarginOptions = {
Top = "80px",
Bottom = "100px",
//left and right do not have an impact
Left = "100px",
Right = "100px"
},

Format = PuppeteerSharp.Media.PaperFormat.A4
};

pdfoptions.FooterTemplate = footerTemplate;
pdfoptions.HeaderTemplate = headerTemplate;

}
else
{
pdfoptions.PrintBackground = _options.PrintBackground;
}

Format = PaperFormat.A4
};

_logger.Log($"Generating PDF document...");
await page.PdfAsync(output, pdfoptions);
await browser.CloseAsync();
_logger.Log($"PDF document is ready.");
pdfoptions.FooterTemplate = footerTemplate;
pdfoptions.HeaderTemplate = headerTemplate;
}
else
{
pdfoptions.PrintBackground = _options.PrintBackground;
}

_logger.Log($"PDF created at: {output}");
return output;

_logger.Log("Generating PDF document...");
await page.PdfAsync(output, pdfoptions);
await browser.CloseAsync();
_logger.Log("PDF document is ready.");
}

_logger.Log($"PDF created at: {output}");
return output;
}
}
}
18 changes: 9 additions & 9 deletions AzureDevOps.WikiPDFExport/azuredevops-export-wiki.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>azuredevops_export_wiki</RootNamespace>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
Expand All @@ -20,15 +20,15 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Leisn.MarkdigToc" Version="0.1.3" />
<PackageReference Include="Markdig" Version="0.28.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.20.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Services.Client" Version="16.170.0" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="16.170.0" />
<PackageReference Include="Microsoft.VisualStudio.Services.InteractiveClient" Version="16.170.0" />
<PackageReference Include="PuppeteerSharp" Version="6.2.0" />
<PackageReference Include="Markdig" Version="0.37.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Services.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.VisualStudio.Services.InteractiveClient" Version="19.225.1" />
<PackageReference Include="PuppeteerSharp" Version="20.0.0" />
</ItemGroup>

</Project>
84 changes: 17 additions & 67 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,70 +1,20 @@
####################################################
# Azure Devops PDF Exporter Image
# DEV GUIDELINES ###################################
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices
#####################################################
# https://mcr.microsoft.com/en-us/product/dotnet/runtime/tags
FROM mcr.microsoft.com/dotnet/runtime:8.0-cbl-mariner2.0 AS base
WORKDIR /app

FROM ubuntu:18.04
# https://mcr.microsoft.com/en-us/product/dotnet/sdk/tags
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["AzureDevOps.WikiPDFExport/azuredevops-export-wiki.csproj", "."]
RUN dotnet restore "azuredevops-export-wiki.csproj"
COPY . .
WORKDIR "AzureDevOps.WikiPDFExport"
#RUN dotnet build "azuredevops-export-wiki.csproj" -c Release -o ./build

RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get -qq -o=Dpkg::Use-Pty=0 update --fix-missing && apt-get -qq -o=Dpkg::Use-Pty=0 install -f -y gconf-service \
libasound2 \
libatk1.0-0 \
libc6 \
libcairo2\
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgcc1 \
libgconf-2-4 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
ca-certificates \
fonts-liberation \
libappindicator1 \
libnss3 \
lsb-release \
xdg-utils \
wget \
libgbm-dev \
ttf-ancient-fonts\
# Tidy up
&& apt-get -qq autoremove -y \
&& rm -rf /var/lib/apt/lists/*
#FROM build AS publish
RUN dotnet publish "azuredevops-export-wiki.csproj" -r linux-x64 --configuration Release -o /src/publish /p:UseAppHost=true /p:PublishReadyToRun=true /p:PublishSingleFile=true

RUN export DEBIAN_FRONTEND=noninteractive \
# Install Microsoft package feed
&& wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
&& dpkg -i packages-microsoft-prod.deb \
&& rm packages-microsoft-prod.deb \
\
# Install .NET
&& apt-get update \
&& apt-get install -y apt-transport-https \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
dotnet-runtime-6.0 \
\
# Cleanup
&& rm -rf /var/lib/apt/lists/*

COPY ./output/linux-x64/azuredevops-export-wiki /usr/local/bin
FROM base AS final
WORKDIR /app
COPY --from=build /src/publish .
ENTRYPOINT ["dotnet", "azuredevops-export-wiki"]
Loading