Skip to content

Handle cert dir mounts; add Docker restart#51

Merged
ewassef merged 1 commit intomainfrom
hotfix/bugs
Jan 28, 2026
Merged

Handle cert dir mounts; add Docker restart#51
ewassef merged 1 commit intomainfrom
hotfix/bugs

Conversation

@ewassef
Copy link
Contributor

@ewassef ewassef commented Jan 28, 2026

Detect and remove incorrectly-created certificate directories (common on Mac when Docker mounts missing paths) and validate certificate files before use. Add Validate/Fix helpers in UpdateClustersCommand and ReverseProxyClient, update reverse proxy creation to restart a stopped proxy or create it with proper file mappings, and restart the proxy when configs change. Expose Restart on IDockerEngine and implement it in FallbackDockerEngine and LocalDockerClient. Also switch Docker container lookups to use name filters/NAMES instead of the vega-component label, fix async removal/start calls, adjust CoreDNS rewrite insertion to before the kubernetes block, and update tests. Minor change: update registry image reference to ghcr.io/project-zot/zot:v2.1.0.

Detect and remove incorrectly-created certificate directories (common on Mac when Docker mounts missing paths) and validate certificate files before use. Add Validate/Fix helpers in UpdateClustersCommand and ReverseProxyClient, update reverse proxy creation to restart a stopped proxy or create it with proper file mappings, and restart the proxy when configs change. Expose Restart on IDockerEngine and implement it in FallbackDockerEngine and LocalDockerClient. Also switch Docker container lookups to use name filters/NAMES instead of the vega-component label, fix async removal/start calls, adjust CoreDNS rewrite insertion to before the kubernetes block, and update tests. Minor change: update registry image reference to ghcr.io/project-zot/zot:v2.1.0.
Copilot AI review requested due to automatic review settings January 28, 2026 21:17
@ewassef ewassef merged commit d16f7c5 into main Jan 28, 2026
6 checks passed
@ewassef ewassef deleted the hotfix/bugs branch January 28, 2026 21:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances certificate handling for Docker mounts and adds container restart functionality. On macOS, Docker can incorrectly create directories when mounting paths that don't exist; this change detects and removes such directories before attempting to use certificate files. The PR also adds a Restart method to the Docker engine interface and switches from nginx -s reload to container restart for applying configuration changes. Additionally, it migrates Docker container lookups from label-based filtering to name-based filtering and updates the CoreDNS configuration patching to insert rewrite rules before (instead of after) the kubernetes block.

Changes:

  • Added certificate path validation and directory cleanup logic in ReverseProxyClient and UpdateClustersCommand
  • Implemented Restart method in IDockerEngine, LocalDockerClient, and FallbackDockerEngine
  • Updated reverse proxy creation to restart stopped containers instead of recreating them
  • Changed Docker container lookup from vega-component label to name-based filtering
  • Modified CoreDNS rewrite insertion to occur before the kubernetes block for proper DNS evaluation order
  • Updated registry image to multi-architecture variant (ghcr.io/project-zot/zot:v2.1.0)

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
cli/src/Vdk/Services/ReverseProxyClient.cs Added certificate validation/fix logic, updated Create() to handle stopped containers, changed ReloadConfigs() to use Restart, updated CoreDNS patching logic
cli/src/Vdk/Services/LocalDockerClient.cs Switched from label-based to name-based container lookups, fixed async await on container removal, added Restart implementation
cli/src/Vdk/Services/IDockerEngine.cs Added Restart method to interface
cli/src/Vdk/Services/FallbackDockerEngine.cs Implemented Restart method using docker restart command
cli/src/Vdk/Commands/UpdateClustersCommand.cs Added FixCertificatePathIfDirectory helper to detect and remove incorrectly-created cert directories
cli/src/Vdk/Constants/Containers.cs Updated registry image to multi-architecture variant
cli/tests/Vdk.Tests/ReverseProxyClientTests.cs Updated test to verify CoreDNS rewrite insertion before kubernetes block
Comments suppressed due to low confidence (1)

cli/src/Vdk/Services/LocalDockerClient.cs:95

  • Multiple async operations are being blocked synchronously using GetAwaiter().GetResult(). This pattern can potentially cause deadlocks in certain contexts and is generally considered an anti-pattern.

While this may work in the current context, consider making the Exists, Delete, and Restart methods async (returning Task<bool>) to properly propagate async/await throughout the call chain. This would align better with the async nature of the Docker.DotNet library and avoid potential threading issues.

    public bool Exists(string name, bool checkRunning = true)
    {
        IList<ContainerListResponse> containers = _dockerClient.Containers.ListContainersAsync(
            new ContainersListParameters()
            {
                Filters = new Dictionary<string, IDictionary<string, bool>> { { "name", new Dictionary<string, bool> { { name, true } } } },
                All = true,
            }).GetAwaiter().GetResult();
        var container = containers.FirstOrDefault(x => x.Names.Any(n => n.TrimStart('/') == name));

        if (container == null) return false;
        if (checkRunning == false) return true;

        return container.State == "running" ||
               // this should be started, lets do it
               _dockerClient.Containers.StartContainerAsync(container.ID, new ContainerStartParameters() { }).GetAwaiter().GetResult();
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +115 to 134
private bool ValidateAndFixCertificatePath(string path)
{
// Check if path exists as a directory (incorrect state)
if (Directory.Exists(path))
{
_console.WriteWarning($"Certificate path '{path}' exists as a directory instead of a file. Removing...");
try
{
_docker.Run(Containers.ProxyImage, Containers.ProxyName,
new[] { new PortMapping() { HostPort = ReverseProxyHostPort, ContainerPort = 443 } },
null,
new[]
{
new FileMapping() { Destination = "/etc/nginx/conf.d/vega.conf", Source = conf.FullName },
new FileMapping() { Destination = "/etc/certs/fullchain.pem", Source = fullChain.FullName },
new FileMapping() { Destination = "/etc/certs/privkey.pem", Source = privKey.FullName },
},
null);
Directory.Delete(path, recursive: true);
}
catch (Exception e)
catch (Exception ex)
{
Console.WriteLine($"Error creating reverse proxy: {e}");
_console.WriteError($"Failed to remove directory '{path}': {ex.Message}");
return false;
}
}
else
{
_console.WriteLine("Vega VDK Proxy already created");
}

// Now check if the file exists
return File.Exists(path);
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The certificate validation logic is duplicated between ValidateAndFixCertificatePath() in ReverseProxyClient.cs and FixCertificatePathIfDirectory() in UpdateClustersCommand.cs. Both methods perform the same task: checking if a path exists as a directory and removing it.

Consider extracting this logic into a shared utility method or service to avoid duplication and ensure consistency. This would make maintenance easier and reduce the risk of the two implementations diverging over time.

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +148
public bool Restart(string name)
{
var container = _dockerClient.Containers.ListContainersAsync(
new ContainersListParameters()
{
Filters = new Dictionary<string, IDictionary<string, bool>> { { "name", new Dictionary<string, bool> { { name, true } } } },
All = true,
}).GetAwaiter().GetResult().FirstOrDefault(x => x.Names.Any(n => n.TrimStart('/') == name));

if (container == null) return false;
_dockerClient.Containers.RestartContainerAsync(container.ID, new ContainerRestartParameters()).GetAwaiter().GetResult();
return true;
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Restart method lacks test coverage. Consider adding tests for this new functionality, especially since the codebase has tests for other IDockerEngine implementations (e.g., FallbackDockerEngineTests.cs tests Run, Exists, Delete, Stop, and Exec operations).

Copilot uses AI. Check for mistakes.
Comment on lines +115 to 134
private bool ValidateAndFixCertificatePath(string path)
{
// Check if path exists as a directory (incorrect state)
if (Directory.Exists(path))
{
_console.WriteWarning($"Certificate path '{path}' exists as a directory instead of a file. Removing...");
try
{
_docker.Run(Containers.ProxyImage, Containers.ProxyName,
new[] { new PortMapping() { HostPort = ReverseProxyHostPort, ContainerPort = 443 } },
null,
new[]
{
new FileMapping() { Destination = "/etc/nginx/conf.d/vega.conf", Source = conf.FullName },
new FileMapping() { Destination = "/etc/certs/fullchain.pem", Source = fullChain.FullName },
new FileMapping() { Destination = "/etc/certs/privkey.pem", Source = privKey.FullName },
},
null);
Directory.Delete(path, recursive: true);
}
catch (Exception e)
catch (Exception ex)
{
Console.WriteLine($"Error creating reverse proxy: {e}");
_console.WriteError($"Failed to remove directory '{path}': {ex.Message}");
return false;
}
}
else
{
_console.WriteLine("Vega VDK Proxy already created");
}

// Now check if the file exists
return File.Exists(path);
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new ValidateAndFixCertificatePath method lacks test coverage. This is an important utility function that modifies the filesystem by detecting and removing incorrectly-created certificate directories.

The codebase has extensive tests for ReverseProxyClient in ReverseProxyClientTests.cs (testing methods like PatchCoreDns, InitConfFile, etc.), so adding tests for this new method would align with the existing testing patterns. Consider adding tests that verify:

  1. The method returns false when the path is a directory and can be removed
  2. The method returns true when the path is a valid file
  3. The method handles errors gracefully when directory removal fails

Copilot uses AI. Check for mistakes.
Comment on lines +364 to +367
if (!ValidateAndFixCertificatePath(fullChainPath) ||
!ValidateAndFixCertificatePath(privKeyPath))
{
_console.WriteError("Certificate files are missing. Cannot create TLS secret.");
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message "Certificate files are missing. Cannot create TLS secret." could be more specific about which certificate file(s) are missing. Since the validation checks two separate files (fullchain.pem and privkey.pem), indicating which one failed would help users troubleshoot the issue more efficiently.

Consider updating the error message to specify which file(s) are missing or failed validation, for example: "Certificate file is missing or invalid: {path}"

Suggested change
if (!ValidateAndFixCertificatePath(fullChainPath) ||
!ValidateAndFixCertificatePath(privKeyPath))
{
_console.WriteError("Certificate files are missing. Cannot create TLS secret.");
var invalidCertificatePaths = new List<string>();
if (!ValidateAndFixCertificatePath(fullChainPath))
{
invalidCertificatePaths.Add(fullChainPath);
}
if (!ValidateAndFixCertificatePath(privKeyPath))
{
invalidCertificatePaths.Add(privKeyPath);
}
if (invalidCertificatePaths.Count > 0)
{
_console.WriteError(
$"Certificate file(s) are missing or invalid: {string.Join(", ", invalidCertificatePaths)}. Cannot create TLS secret.");

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +88
public bool Restart(string name)
{
return RunProcess("docker", $"restart {name}", out _, out _);
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Restart method lacks test coverage. The existing test file FallbackDockerEngineTests.cs has tests for other Docker operations (Run, Exists, Delete, Stop, Exec), but doesn't include tests for the newly added Restart functionality.

Consider adding a test that verifies the Restart method works correctly, similar to the existing tests for other Docker operations.

Copilot uses AI. Check for mistakes.
if (!ValidateAndFixCertificatePath(fullChain.FullName) ||
!ValidateAndFixCertificatePath(privKey.FullName))
{
_console.WriteError("Certificate files are missing. Please ensure Certs/fullchain.pem and Certs/privkey.pem exist.");
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling here returns early when certificate files are missing, which prevents the Docker container from being created. However, the error message suggests the user should "ensure" the files exist, but doesn't provide guidance on how to create or obtain these certificate files.

Consider enhancing the error message to include actionable instructions, such as pointing to documentation on how to generate or obtain the required certificate files, or providing a command to generate self-signed certificates for development purposes.

Suggested change
_console.WriteError("Certificate files are missing. Please ensure Certs/fullchain.pem and Certs/privkey.pem exist.");
_console.WriteError("Certificate files are missing. The reverse proxy cannot be started without TLS certificates.");
_console.WriteError("Expected files: Certs/fullchain.pem and Certs/privkey.pem");
_console.WriteError("For local development, you can create self-signed certificates with:");
_console.WriteError(" mkdir -p Certs && openssl req -x509 -nodes -newkey rsa:2048 -keyout Certs/privkey.pem -out Certs/fullchain.pem -days 365");
_console.WriteError("For production, use certificates issued by a trusted CA and refer to the Vega VDK documentation on TLS certificates.");

Copilot uses AI. Check for mistakes.
Comment on lines 92 to 94
return container.State == "running" ||
// this should be started, lets do it
_dockerClient.Containers.StartContainerAsync(container.ID, new ContainerStartParameters() { }).GetAwaiter().GetResult();
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Exists method has inconsistent behavior between implementations of IDockerEngine:

  • FallbackDockerEngine.Exists() only checks existence without side effects
  • LocalDockerClient.Exists() attempts to auto-start stopped containers when checkRunning=true (line 92-94)

This inconsistency can lead to different behavior depending on which Docker engine is in use. The auto-start behavior in LocalDockerClient appears to be the problematic one, as it prevents the restart logic in ReverseProxyClient.Create() from working correctly.

Consider removing the auto-start behavior from LocalDockerClient.Exists() to maintain consistency across implementations and make the method behavior match its name (it should only check existence, not start containers).

Suggested change
return container.State == "running" ||
// this should be started, lets do it
_dockerClient.Containers.StartContainerAsync(container.ID, new ContainerStartParameters() { }).GetAwaiter().GetResult();
// When checkRunning is true, only report whether the container is running;
// do not start or otherwise modify container state.
return container.State == "running";

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant