in src/library/Connect/KubernetesRemoteEnvironmentManager.cs [720:813]
private async Task<(V1Pod pod, V1Container container)> _PatchDeploymentAsync(
RemoteContainerConnectionDetails remoteContainerConnectionDetails,
ILocalProcessConfig localProcessConfig,
CancellationToken cancellationToken)
{
using (var perfLogger = _log.StartPerformanceLogger(Events.KubernetesRemoteEnvironmentManager.AreaName, Events.KubernetesRemoteEnvironmentManager.Operations.PatchDeployment))
{
var namespaceName = remoteContainerConnectionDetails.NamespaceName;
var deploymentName = remoteContainerConnectionDetails.DeploymentName;
// Get pods for the deployment, if there is no patch required we use the running pod under existing deployment
var existingPods = (await _kubernetesClient.ListPodsForDeploymentAsync(namespaceName, deploymentName, cancellationToken))?.Items;
if (existingPods == null)
{
throw new InvalidOperationException(string.Format(Resources.FailedToListPodsFormat, namespaceName));
}
// Patch is null if there is no change in the deployment spec
var (patch, reversePatch) = this._GetDeploymentJsonPatch(remoteContainerConnectionDetails.Deployment, remoteContainerConnectionDetails.Container, _imageProvider.DevHostImage, localProcessConfig);
V1Pod result = null;
if (patch != null)
{
var serializedPatch = "";
try
{
serializedPatch = JsonHelpers.SerializeObject(patch);
await _kubernetesClient.PatchV1DeploymentAsync(namespaceName, deploymentName, new V1Patch(serializedPatch, PatchType.JsonPatch), cancellationToken: cancellationToken);
}
catch (Exception ex)
{
serializedPatch = StringManipulation.RemovePrivateKeyIfNeeded(serializedPatch);
_log.Error($"Patch deployment {namespaceName}/{remoteContainerConnectionDetails.DeploymentName} failed. Patch is {serializedPatch}, {ex.Message}");
throw new UserVisibleException(_operationContext, ex, Resources.PatchResourceFailedFormat, KubernetesResourceType.Deployment.ToString(), namespaceName, deploymentName, serializedPatch, ex.Message);
}
var deploymentPatch = new DeploymentPatch(remoteContainerConnectionDetails.Deployment, reversePatch);
try
{
// Deploy remote restore job
await _remoteRestoreJobDeployer.CreateRemoteRestoreJobAsync(deploymentName, namespaceName, deploymentPatch, cancellationToken);
_patchState.AddDeploymentPatch(deploymentPatch);
}
catch (Exception ex)
{
// Try to clean up patched Deployment
this._ReportProgress(EventLevel.Warning, Resources.RestoringResourcePatchMessage, KubernetesResourceType.Deployment.ToString());
await _workloadRestorationService.Value.RestoreDeploymentPatchAsync(
deploymentPatch,
cancellationToken,
progressCallback: p => this._ReportProgress(p.Message, p.Level),
noThrow: true);
_log.Error($"Failed to deploy remote restore job for deployment {namespaceName}/{deploymentName}. {ex.Message}");
throw new UserVisibleException(_operationContext, ex, Resources.FailedToDeployRemoteRestoreJobFormat, namespaceName, deploymentName, ex.Message);
}
// Get new pod running under the deployment
V1Pod newPod = null;
while (true)
{
await Task.Delay(200);
var pods = await _kubernetesClient.ListPodsForDeploymentAsync(namespaceName, deploymentName, cancellationToken);
newPod = pods.Items.Where(p => existingPods.All(p1 => p1.Metadata.CreationTimestamp < p.Metadata.CreationTimestamp)).FirstOrDefault();
if (newPod != null)
{
break;
}
}
result = await _WaitForPodToRunAsync(newPod, maxWaitTime: TimeSpan.FromMinutes(5), cancellationToken: cancellationToken);
this._ReportProgress(Resources.ResourcePatchedForAgentFormat, KubernetesResourceType.Deployment.ToString(), namespaceName, deploymentName);
}
else
{
// If there's nothing to patch, it may mean we crashed during the last run.
// Try to get an existing patch state from the cluster.
var existingPatch = await _remoteRestoreJobDeployer.TryGetExistingPatchInfoAsync<DeploymentPatch>(deploymentName, namespaceName, cancellationToken);
if (existingPatch != null)
{
_patchState.AddDeploymentPatch(existingPatch);
}
result = existingPods.Where(p => !this._IsPodTerminated(p) && p.Status?.StartTime != null).OrderBy(p => p.Status.StartTime).LastOrDefault();
}
if (result != null)
{
perfLogger.SetSucceeded();
}
if (result == null)
{
throw new UserVisibleException(_operationContext, Resources.FailedToGetRunningPodInDeploymentFormat, deploymentName, namespaceName);
}
var container = result.Spec.Containers.Where(c => StringComparer.OrdinalIgnoreCase.Equals(c.Name, remoteContainerConnectionDetails.ContainerName)).First();
return (result, container);
}
}