Skip to content

Instantly share code, notes, and snippets.

@ilyalukyanov
Last active January 21, 2020 22:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ilyalukyanov/c2c37d7a0fae85c177e8da6999de89e3 to your computer and use it in GitHub Desktop.
Save ilyalukyanov/c2c37d7a0fae85c177e8da6999de89e3 to your computer and use it in GitHub Desktop.
Shim to add support for Helm 3 and ACR into Octopus Deploy 2019.9.12
  1. Create a new solution and copy-paste this code.
  2. Repalce ### YOUR_KEY ### and ### YOUR_IV ### with your values. This is done so that ACR credentials aren't stored open. The whole AES part is functionally unnecessary and isn't in fact secure, jsut better than nothing.
  3. Find where the original helm 3 executable is located on your OcropusDeploy server.
  4. Rename it from helm.exe to _helm.exe
  5. Copy the executable built from this repository with dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true into the same folder and name it helm.exe.
  6. PROFIT
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace HelmProxy
{
internal static class Program
{
private const string ThisApplicationHomeDir = @"C:\helm-proxy";
static async Task Main(string[] args)
{
if (!Directory.Exists(ThisApplicationHomeDir)) Directory.CreateDirectory(ThisApplicationHomeDir);
var argsString = string.Join(" ", args);
using var aes = Aes.Create();
aes.Key = Convert.FromBase64String("### YOUR_KEY ###");
aes.IV = Convert.FromBase64String("### YOUR_IV ###");
var exitCode = -1;
var command = args.FirstOrDefault();
if (command != null)
{
switch (command)
{
case "init":
// Unsupported, just silently ignoring this command
return;
case "repo":
{
/*
* ACR doesn't allow to do repo add with ACR credentials. Instead it requires calling `az acr helm repo add` that creates does pretty much the same except with a temporary token.
* I wasn't sure if Octopus will call repo add each time a package is downloaded, so decided to mimic its behaviour.
* Feel free to experiment.
*
* Parsing command:
* repo add --home C:\Windows\TEMP\helm\68394d8c9e3bd14448357c8b\helm --username <user_name> --password <pwd> <feed_name> <repo_url>
*/
var username = args[5];
var password = args[7];
var feedName = args[8];
var host = new UriBuilder(args[9]) {Path = ""}.ToString();
var (fileName, rawSize, encSize) =
await HelmRepoAddAsync(aes, feedName, host, username, password);
return;
}
case "fetch":
{
/*
* Converting this: fetch --home <home> --version <version> --destination <destination> <feed_name>/<package_name>
* Into this: fetch https://<feed_name>/.azurecr.io/helm/v1/repo/_blobs/<package_name>.tgz --version <version> --destination <destination>
*/
var packageAddrMatches = Regex.Matches(args[7], @"^([^/]+)/([^/]+)$")[0].Groups;
var (host, username, password) =
await HelmRepoGetAsync(aes, Convert.ToString(packageAddrMatches[1]));
var packageUrl = $"{host}/helm/v1/repo/_blobs/{packageAddrMatches[2]}-{args[4]}.tgz";
argsString = string.Join(" ", args[0], packageUrl, args[5], args[6], $"--username {username}",
$"--password {password}");
break;
}
}
}
// This presumes you have renamed helm.exe to _helm.exe in its original location
using var process = Process.Start(
new ProcessStartInfo("_helm", argsString)
{
RedirectStandardError = true,
RedirectStandardOutput = true
});
process.WaitForExit();
var outputText = await process.StandardOutput.ReadToEndAsync();
if (outputText.Length > 0) await Console.Out.WriteAsync(outputText);
var errorText = await process.StandardError.ReadToEndAsync();
if (errorText.Length > 0) await Console.Error.WriteAsync(errorText);
exitCode = process.ExitCode;
Environment.Exit(exitCode);
}
private static async Task<(string fileName, int rawPayloadLength, int encryptedPayloadLength)> HelmRepoAddAsync(
Aes aes, string feedName, string host, string username, string password)
{
var tempRepoCredentialsFile = Path.Combine(ThisApplicationHomeDir,
$"helm-repo-{feedName}.txt");
using var encryptor = aes.CreateEncryptor();
await using var helmRepoFile = File.Create(tempRepoCredentialsFile);
await using var aesStream =
new CryptoStream(helmRepoFile, encryptor, CryptoStreamMode.Write);
var repoPayload = Encoding.UTF8.GetBytes(string.Join(" ", host, username, password));
await aesStream.WriteAsync(repoPayload, 0, repoPayload.Length);
aesStream.FlushFinalBlock();
await aesStream.FlushAsync();
await helmRepoFile.FlushAsync();
return (tempRepoCredentialsFile, repoPayload.Length, (int) helmRepoFile.Length);
}
private static async Task<(string host, string username, string password)> HelmRepoGetAsync(Aes aes,
string feedName)
{
var tempRepoCredentialsFile = Path.Combine(ThisApplicationHomeDir,
$"helm-repo-{feedName}.txt");
using var decryptor = aes.CreateDecryptor();
await using var helmRepoFile = File.OpenRead(tempRepoCredentialsFile);
await using var aesStream =
new CryptoStream(helmRepoFile, decryptor, CryptoStreamMode.Read);
await using var memoryStream = new MemoryStream();
await aesStream.CopyToAsync(memoryStream);
await memoryStream.FlushAsync();
var repoDetails = Encoding.UTF8.GetString(memoryStream.ToArray()).Split(" ");
return (repoDetails[0], repoDetails[1], repoDetails[2]);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment