Skip to content

Commit

Permalink
Merge pull request #14 from microsoft/misc-updates
Browse files Browse the repository at this point in the history
Adding SpaceFX Secrets generation
  • Loading branch information
KevinDMack authored Aug 4, 2024
2 parents 3271926 + 34af4db commit 561ce4d
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 11 deletions.
72 changes: 62 additions & 10 deletions src/Services/ScheduleProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@ private void WaitForFileToFinishCopying(string filePath) {
|| _response.DeployRequest.DeployAction == MessageFormats.PlatformServices.Deployment.DeployRequest.Types.DeployActions.Create
|| _response.DeployRequest.DeployAction == MessageFormats.PlatformServices.Deployment.DeployRequest.Types.DeployActions.Delete) {
WaitForFileToFinishCopying(Path.Combine(_scheduleImportDirectory, _response.DeployRequest.YamlFileContents));

string yamlFilePath = Path.Combine(_scheduleImportDirectory, _response.DeployRequest.YamlFileContents);
_response.DeployRequest.YamlFileContents = File.ReadAllText(Path.Combine(_scheduleImportDirectory, _response.DeployRequest.YamlFileContents));
File.Delete(yamlFilePath);
}


Expand Down Expand Up @@ -473,24 +476,53 @@ private MessageFormats.PlatformServices.Deployment.DeployResponse ValidatePrereq
if (request.DeployAction == MessageFormats.PlatformServices.Deployment.DeployRequest.Types.DeployActions.Apply
|| request.DeployAction == MessageFormats.PlatformServices.Deployment.DeployRequest.Types.DeployActions.Create
|| request.DeployAction == MessageFormats.PlatformServices.Deployment.DeployRequest.Types.DeployActions.Delete) {
if (string.IsNullOrWhiteSpace(request.YamlFileContents))

if (string.IsNullOrWhiteSpace(request.YamlFileContents)) {
errorFields.Add(nameof(request.YamlFileContents));
} else {
// This lets the schedule retry next go round
if (!File.Exists(Path.Combine(_scheduleImportDirectory, request.YamlFileContents))) {
throw new FileNotFoundException($"YamlFile '{request.YamlFileContents}' does not exist at {_scheduleImportDirectory}.", fileName: request.YamlFileContents);
}

// Wait for the file to finish copying or timeout if we didn't receive one
try {
WaitForFileToFinishCopying(Path.Combine(_scheduleImportDirectory, request.YamlFileContents));
} catch (TimeoutException) {
// Only trip an error if the file is required
if (request.AppContextFile.Required == true) {
throw new FileNotFoundException($"YamlFile '{request.YamlFileContents}' still copying to {_scheduleImportDirectory}.", fileName: request.YamlFileContents);
}
}
}


System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex("[^a-zA-Z0-9_-]");
if (regex.IsMatch(Path.GetFileNameWithoutExtension(request.YamlFileContents))) {
response.ResponseHeader.Message += "YamlFileContents value invalid (special characters founds). (RegEx matched on '[^a-zA-Z0-9_]').";
errorFields.Add(nameof(request.YamlFileContents));
}

if (!File.Exists(Path.Combine(_scheduleImportDirectory, request.YamlFileContents))) {
response.ResponseHeader.Message += $"YamlFile '{request.YamlFileContents}' does not exist at {_scheduleImportDirectory}.";
errorFields.Add(nameof(request.YamlFileContents));
}
}

if (request.AppContainerImage != null) {
if (string.IsNullOrWhiteSpace(request.AppContainerImage.TarballFileName))
if (string.IsNullOrWhiteSpace(request.AppContainerImage.TarballFileName)) {
errorFields.Add(nameof(request.AppContainerImage.TarballFileName));
} else {
// This lets the schedule retry next go round
if (!File.Exists(Path.Combine(_scheduleImportDirectory, request.AppContainerImage.TarballFileName))) {
throw new FileNotFoundException($"AppContainerImage Tarball '{request.AppContainerImage.TarballFileName}' does not exist at {_scheduleImportDirectory}.", fileName: request.AppContainerImage.TarballFileName);
}

// Wait for the file to finish copying or timeout if we didn't receive one
try {
WaitForFileToFinishCopying(Path.Combine(_scheduleImportDirectory, request.AppContainerImage.TarballFileName));
} catch (TimeoutException) {
// Only trip an error if the file is required
if (request.AppContextFile.Required == true) {
throw new FileNotFoundException($"DockerFile '{request.AppContainerImage.TarballFileName}' still copying to {_scheduleImportDirectory}.", fileName: request.AppContainerImage.TarballFileName);
}
}
}

if (string.IsNullOrWhiteSpace(request.AppContainerImage.DestinationRepository))
errorFields.Add(nameof(request.AppContainerImage.DestinationRepository));
Expand All @@ -500,8 +532,24 @@ private MessageFormats.PlatformServices.Deployment.DeployResponse ValidatePrereq
}

if (request.AppContainerBuild != null) {
if (string.IsNullOrWhiteSpace(request.AppContainerBuild.DockerFile))
if (string.IsNullOrWhiteSpace(request.AppContainerBuild.DockerFile)) {
errorFields.Add(nameof(request.AppContainerBuild.DockerFile));
} else {
// This lets the schedule retry next go round
if (!File.Exists(Path.Combine(_scheduleImportDirectory, request.AppContainerBuild.DockerFile))) {
throw new FileNotFoundException($"DockerFile'{request.AppContainerBuild.DockerFile}' does not exist at {_scheduleImportDirectory}.", fileName: request.AppContainerBuild.DockerFile);
}

// Wait for the file to finish copying or timeout if we didn't receive one
try {
WaitForFileToFinishCopying(Path.Combine(_scheduleImportDirectory, request.AppContainerBuild.DockerFile));
} catch (TimeoutException) {
// Only trip an error if the file is required
if (request.AppContextFile.Required == true) {
throw new FileNotFoundException($"DockerFile '{request.AppContainerBuild.DockerFile}' still copying to {_scheduleImportDirectory}.", fileName: request.AppContainerBuild.DockerFile);
}
}
}

if (string.IsNullOrWhiteSpace(request.AppContainerBuild.DestinationRepository))
errorFields.Add(nameof(request.AppContainerBuild.DestinationRepository));
Expand All @@ -514,14 +562,18 @@ private MessageFormats.PlatformServices.Deployment.DeployResponse ValidatePrereq
if (string.IsNullOrWhiteSpace(request.AppContextFile.FileName)) {
errorFields.Add(nameof(request.AppContextFile.FileName));
} else {
// This lets the schedule retry next go round
if (!File.Exists(Path.Combine(_scheduleImportDirectory, request.AppContextFile.FileName))) {
throw new FileNotFoundException($"AppContextFile '{request.AppContextFile.FileName}' does not exist at {_scheduleImportDirectory}.", fileName: request.AppContextFile.FileName);
}

// Wait for the file to finish copying or timeout if we didn't receive one
try {
WaitForFileToFinishCopying(Path.Combine(_scheduleImportDirectory, request.AppContextFile.FileName));
} catch (TimeoutException) {
// Only trip an error if the file is required
if (request.AppContextFile.Required == true) {
response.ResponseHeader.Message += $"AppContextFile '{request.AppContextFile.FileName}' is still being copied. Please wait for the file to finish copying.";
errorFields.Add(nameof(request.AppContextFile.FileName));
throw new FileNotFoundException($"AppContextFile '{request.AppContextFile.FileName}' still copying to {_scheduleImportDirectory}.", fileName: request.AppContextFile.FileName);
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/Utils/K8sClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ private void PatchViaYamlObject(IKubernetesObject yamlObject) {
case V1PersistentVolume pv:
_k8sClient.PatchPersistentVolume(new V1Patch(pv, V1Patch.PatchType.MergePatch), name: pv.Metadata.Name);
break;
case V1Secret secret:
_k8sClient.PatchNamespacedSecret(new V1Patch(secret, V1Patch.PatchType.MergePatch), name: secret.Metadata.Name, namespaceParameter: secret.Metadata.NamespaceProperty);
break;
case V1ConfigMap cm:
_k8sClient.PatchNamespacedConfigMap(new V1Patch(cm, V1Patch.PatchType.MergePatch), name: cm.Metadata.Name, namespaceParameter: cm.Metadata.NamespaceProperty);
break;
Expand Down Expand Up @@ -230,6 +233,8 @@ private void PatchViaYamlObject(IKubernetesObject yamlObject) {
if (job.Metadata == null || string.IsNullOrEmpty(job.Metadata.NamespaceProperty)) { throw new NullReferenceException("Metadata.NamespaceProperty is null or empty"); }
_k8sClient.PatchNamespacedJob(new V1Patch(job, V1Patch.PatchType.MergePatch), name: job.Metadata.Name, namespaceParameter: job.Metadata.NamespaceProperty);
break;
default:
throw new Exception(string.Format($"Unknown object type: {yamlObject.GetType()}"));
}
} catch (k8s.Autorest.HttpOperationException ex) {
if (ex.Response.StatusCode == System.Net.HttpStatusCode.UnprocessableEntity) {
Expand Down Expand Up @@ -259,6 +264,9 @@ private void DeleteViaYamlObject(IKubernetesObject yamlObject) {
case V1PersistentVolume pv:
_k8sClient.DeletePersistentVolume(name: pv.Metadata.Name);
break;
case V1Secret secret:
_k8sClient.DeleteNamespacedSecret(name: secret.Metadata.Name, namespaceParameter: secret.Metadata.NamespaceProperty);
break;
case V1ConfigMap cm:
if (cm.Metadata == null || string.IsNullOrEmpty(cm.Metadata.NamespaceProperty)) { throw new NullReferenceException("Metadata.NamespaceProperty is null or empty"); }
_k8sClient.DeleteNamespacedConfigMap(name: cm.Metadata.Name, namespaceParameter: cm.Metadata.NamespaceProperty);
Expand Down Expand Up @@ -292,6 +300,8 @@ private void DeleteViaYamlObject(IKubernetesObject yamlObject) {
if (job.Metadata == null || string.IsNullOrEmpty(job.Metadata.NamespaceProperty)) { throw new NullReferenceException("Metadata.NamespaceProperty is null or empty"); }
_k8sClient.DeleteNamespacedJob(name: job.Metadata.Name, namespaceParameter: job.Metadata.NamespaceProperty, propagationPolicy: "Background");
break;
default:
throw new Exception(string.Format($"Unknown object type: {yamlObject.GetType()}"));
}
} catch (k8s.Autorest.HttpOperationException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound) {
// We can ignore the error if we're trying to delete something and it's not found.
Expand All @@ -312,6 +322,10 @@ private void CreateViaYamlObject(IKubernetesObject yamlObject) {
case V1Namespace ns:
_k8sClient.CreateNamespace(body: ns);
break;
case V1Secret secret:
if (secret.Metadata == null || string.IsNullOrEmpty(secret.Metadata.NamespaceProperty)) { throw new NullReferenceException("Metadata.NamespaceProperty is null or empty"); }
_k8sClient.CreateNamespacedSecret(body: secret, namespaceParameter: secret.Metadata.NamespaceProperty);
break;
case V1ConfigMap cm:
if (cm.Metadata == null || string.IsNullOrEmpty(cm.Metadata.NamespaceProperty)) { throw new NullReferenceException("Metadata.NamespaceProperty is null or empty"); }
_k8sClient.CreateNamespacedConfigMap(body: cm, namespaceParameter: cm.Metadata.NamespaceProperty);
Expand Down Expand Up @@ -342,6 +356,8 @@ private void CreateViaYamlObject(IKubernetesObject yamlObject) {
if (job.Metadata == null || string.IsNullOrEmpty(job.Metadata.NamespaceProperty)) { throw new NullReferenceException("Metadata.NamespaceProperty is null or empty"); }
_k8sClient.CreateNamespacedJob(body: job, namespaceParameter: job.Metadata.NamespaceProperty);
break;
default:
throw new Exception(string.Format($"Unknown object type: {yamlObject.GetType()}"));
}
} catch (Exception ex) {
_logger.LogError("Failed to create object of type '{yamlKind}'. Error: {error}", yamlObject.Kind, ex.Message);
Expand Down
32 changes: 31 additions & 1 deletion src/Utils/TemplateUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public TemplateUtil(ILogger<TemplateUtil> logger, IServiceProvider serviceProvid
internal List<IKubernetesObject> GenerateKubernetesObjectsFromDeployment(MessageFormats.PlatformServices.Deployment.DeployResponse deploymentItem) {
List<IKubernetesObject> returnList = new() {
GenerateAppSettings(deploymentItem),
GenerateServiceAccount(deploymentItem)
GenerateServiceAccount(deploymentItem),
GenerateSecrets(deploymentItem)
};

GeneratePersistentVolumes(deploymentItem).ForEach(pv => returnList.Add(pv));
Expand Down Expand Up @@ -351,6 +352,35 @@ public V1ConfigMap GenerateAppSettings(MessageFormats.PlatformServices.Deploymen
return returnValue;
}

public V1Secret GenerateSecrets(MessageFormats.PlatformServices.Deployment.DeployResponse deploymentItem) {
_logger.LogDebug("Generating secrets template. (AppName: '{AppName}' / trackingId: '{trackingId}' / correlationId: '{correlationId}')'",
deploymentItem.DeployRequest.AppName,
deploymentItem.ResponseHeader.TrackingId,
deploymentItem.ResponseHeader.CorrelationId);

Dictionary<string, string> templateRequest = StandardTemplateRequestItems(deploymentItem);
templateRequest.Add("services.payloadapp.payloadappTemplate.secrets.enabled", "true");

string templateYaml = GenerateTemplate(templateRequest);

V1Secret? returnValue = KubernetesYaml.LoadAllFromString(templateYaml)
.OfType<V1Secret>()
.FirstOrDefault();

if (returnValue == null)
throw new ApplicationException("Failed to generate SpaceFX Secrets");

// The template returns the values in base64 encoded strings. We have to decode them to their byte values.
// Kubernetes normally does this for us on yaml ingession, but we're skipping that and add items via the API
// So we just have to convert the arrays to strings, base64 decode them, then convert them back to byte arrays
foreach (var kvp in returnValue.Data) {
string decodedValue = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(kvp.Value)));
returnValue.Data[kvp.Key] = Encoding.UTF8.GetBytes(decodedValue);
}

return returnValue;
}

public List<V1Volume> GenerateVolumes(MessageFormats.PlatformServices.Deployment.DeployResponse deploymentItem) {
_logger.LogDebug("Generating volumes template. (AppName: '{AppName}' / trackingId: '{trackingId}' / correlationId: '{correlationId}')'",
deploymentItem.DeployRequest.AppName,
Expand Down

0 comments on commit 561ce4d

Please sign in to comment.