From e72c1b037c00604864b36e42f96b6c77fab5f9b6 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 26 Mar 2024 14:42:47 +0100 Subject: [PATCH] Implements api compatiblity checker --- ProjectPlugins/CodexPlugin/ApiChecker.cs | 91 +++++++++++++++++++ ProjectPlugins/CodexPlugin/CodexPlugin.cs | 2 - ProjectPlugins/CodexPlugin/CodexStarter.cs | 5 + ProjectPlugins/CodexPluginPrebuild/Program.cs | 49 +++++++--- 4 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 ProjectPlugins/CodexPlugin/ApiChecker.cs diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs new file mode 100644 index 00000000..5d71ad0c --- /dev/null +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -0,0 +1,91 @@ +using Core; +using KubernetesWorkflow.Types; +using Logging; +using System.Security.Cryptography; +using System.Text; + +namespace CodexPlugin +{ + public class ApiChecker + { + // + private const string OpenApiYamlHash = "5E-B8-2A-E3-61-0C-D6-11-F7-F6-19-4C-F9-35-CA-8B-D1-FF-51-52-1E-E7-A3-7A-5D-0C-2A-3D-50-93-5E-55"; + private const string OpenApiFilePath = "/codex/openapi.yaml"; + private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; + + private const bool Disable = false; + + private const string Warning = + "Warning: CodexPlugin was unable to find the openapi.yaml file in the Codex container. Are you running an old version of Codex? " + + "Plugin will continue as normal, but API compatibility is not guaranteed!"; + + private const string Failure = + "Codex API compatibility check failed! " + + "openapi.yaml used by CodexPlugin does not match openapi.yaml in Codex container. Please update the openapi.yaml in " + + "'ProjectPlugins/CodexPlugin' and rebuild this project. If you wish to disable API compatibility checking, please set " + + $"the environment variable '{DisableEnvironmentVariable}' or set the disable bool in 'ProjectPlugins/CodexPlugin/ApiChecker.cs'."; + + private static bool checkPassed = false; + + private readonly IPluginTools pluginTools; + private readonly ILog log; + + public ApiChecker(IPluginTools pluginTools) + { + this.pluginTools = pluginTools; + log = pluginTools.GetLog(); + + if (string.IsNullOrEmpty(OpenApiYamlHash)) throw new Exception("OpenAPI yaml hash was not inserted by pre-build trigger."); + } + + public void CheckCompatibility(RunningContainers[] containers) + { + if (checkPassed) return; + + Log("CodexPlugin is checking API compatibility..."); + + if (Disable || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DisableEnvironmentVariable))) + { + Log("API compatibility checking has been disabled."); + checkPassed = true; + return; + } + + var workflow = pluginTools.CreateWorkflow(); + var container = containers.First().Containers.First(); + var containerApi = workflow.ExecuteCommand(container, "cat", OpenApiFilePath); + + if (string.IsNullOrEmpty(containerApi)) + { + log.Error(Warning); + + checkPassed = true; + return; + } + + var containerHash = Hash(containerApi); + if (containerHash == OpenApiYamlHash) + { + Log("API compatibility check passed."); + checkPassed = true; + return; + } + + log.Error(Failure); + throw new Exception(Failure); + } + + private string Hash(string file) + { + var fileBytes = Encoding.ASCII.GetBytes(file); + var sha = SHA256.Create(); + var hash = sha.ComputeHash(fileBytes); + return BitConverter.ToString(hash); + } + + private void Log(string msg) + { + log.Log(msg); + } + } +} diff --git a/ProjectPlugins/CodexPlugin/CodexPlugin.cs b/ProjectPlugins/CodexPlugin/CodexPlugin.cs index a7e0f575..99a9337c 100644 --- a/ProjectPlugins/CodexPlugin/CodexPlugin.cs +++ b/ProjectPlugins/CodexPlugin/CodexPlugin.cs @@ -9,8 +9,6 @@ namespace CodexPlugin private readonly IPluginTools tools; private readonly CodexLogLevel defaultLogLevel = CodexLogLevel.Trace; - private const string OpenApiYamlHash = "8B-DD-61-54-42-D7-28-8F-5A-A0-AF-C2-A4-53-A7-08-B6-C7-02-FD-59-1A-01-A9-B4-7D-E4-81-FA-84-23-7F"; - public CodexPlugin(IPluginTools tools) { codexStarter = new CodexStarter(tools); diff --git a/ProjectPlugins/CodexPlugin/CodexStarter.cs b/ProjectPlugins/CodexPlugin/CodexStarter.cs index d10f502a..05db6bdf 100644 --- a/ProjectPlugins/CodexPlugin/CodexStarter.cs +++ b/ProjectPlugins/CodexPlugin/CodexStarter.cs @@ -9,11 +9,14 @@ namespace CodexPlugin { private readonly IPluginTools pluginTools; private readonly CodexContainerRecipe recipe = new CodexContainerRecipe(); + private readonly ApiChecker apiChecker; private DebugInfoVersion? versionResponse; public CodexStarter(IPluginTools pluginTools) { this.pluginTools = pluginTools; + + apiChecker = new ApiChecker(pluginTools); } public RunningContainers[] BringOnline(CodexSetup codexSetup) @@ -25,6 +28,8 @@ namespace CodexPlugin var containers = StartCodexContainers(startupConfig, codexSetup.NumberOfNodes, codexSetup.Location); + apiChecker.CheckCompatibility(containers); + foreach (var rc in containers) { var podInfo = GetPodInfo(rc); diff --git a/ProjectPlugins/CodexPluginPrebuild/Program.cs b/ProjectPlugins/CodexPluginPrebuild/Program.cs index f33246da..5ad5e895 100644 --- a/ProjectPlugins/CodexPluginPrebuild/Program.cs +++ b/ProjectPlugins/CodexPluginPrebuild/Program.cs @@ -3,8 +3,30 @@ public static class Program { private const string OpenApiFile = "../CodexPlugin/openapi.yaml"; - private const string Search = ""; - private const string TargetFile = "CodexPlugin.cs"; + private const string Search = ""; + private const string TargetFile = "ApiChecker.cs"; + + public static void Main(string[] args) + { + Console.WriteLine("Injecting hash of 'openapi.yaml'..."); + + var hash = CreateHash(); + // This hash is used to verify that the Codex docker image being used is compatible + // with the openapi.yaml being used by the Codex plugin. + // If the openapi.yaml files don't match, an exception is thrown. + + SearchAndInject(hash); + + // This program runs as the pre-build trigger for "CodexPlugin". + // You might be wondering why this work isn't done by a shell script. + // This is because this project is being run on many different platforms. + // (Mac, Unix, Win, but also desktop/cloud containers.) + // In order to not go insane trying to make a shell script that works in all possible cases, + // instead we use the one tool that's definitely installed in all platforms and locations + // when you're trying to run this plugin. + + Console.WriteLine("Done!"); + } private static string CreateHash() { @@ -14,23 +36,22 @@ public static class Program return BitConverter.ToString(hash); } - private static void SearchAndReplace(string hash) + private static void SearchAndInject(string hash) { var lines = File.ReadAllLines(TargetFile); - lines = lines.Select(l => l.Replace(Search, hash)).ToArray(); + Inject(lines, hash); File.WriteAllLines(TargetFile, lines); } - public static void Main(string[] args) + private static void Inject(string[] lines, string hash) { - Console.WriteLine("Injecting hash of 'openapi.yaml'..."); - // This hash is used to verify that the Codex docker image being used is compatible - // with the openapi.yaml being used by the Codex plugin. - // If the openapi.yaml files don't match, an exception is thrown. - - var hash = CreateHash(); - SearchAndReplace(hash); - - Console.WriteLine("Done!"); + for (var i = 0; i < lines.Length; i++) + { + if (lines[i].Contains(Search)) + { + lines[i + 1] = $" private const string OpenApiYamlHash = \"{hash}\";"; + return; + } + } } } \ No newline at end of file