-
-
Notifications
You must be signed in to change notification settings - Fork 341
feat: Get Docker endpoint from Docker context #1235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
71994ca
Get the current docker endpoint through the `docker context` command
0xced 027f704
Read the current docker endpoint from the file system
0xced 38cd08a
Deserialize the meta.json file with typed objects
0xced ce075c8
Improve DockerConfigTest.GetCurrentEndpoint test
0xced 8e864ec
Avoid instantiating the default DockerConfig twice
0xced 512784f
Leverage the existing DockerCli class
0xced 7df5dc8
Log the current endpoint
0xced a33f149
Handle the default context
0xced 8ba827b
Handle DOCKER_CONTEXT and DOCKER_HOST environment variables
0xced 2295592
Fix tests on Windows
0xced 9ce89e8
Add Docker CLI reference
0xced 6ccdda9
chore: Add ICustomConfiguration.GetDockerContext
HofmeisterAn 58d6f65
Merge branch 'docker-context' of github.com:0xced/testcontainers-dotn…
HofmeisterAn 5555390
chore: Add comments, increase tests
HofmeisterAn d98e4bc
fix: Do not reference TestcontainersSettings.OS
HofmeisterAn 0fa1b73
chore: Add ThrowIfExecutionFailed
HofmeisterAn 74462e6
fix: Set ExitCode property again
HofmeisterAn 6c70ea8
docs: Add docker context docs
HofmeisterAn a3e1854
docs: Enhance context and logger docs
HofmeisterAn a8862ca
Merge branch 'develop' into 0xced-docker-context
HofmeisterAn e5b7aaf
Make the DockerConfig class actually testable
0xced f5898ec
Actually test reading the current context from Docker config files
0xced 266ee03
Skip ReturnsActiveEndpointWhenDockerContextIsUnset if necessary
0xced afd4fd3
Merge branch 'develop' into docker-context
HofmeisterAn d7dc042
chore: Remove unnecessary config file arg
HofmeisterAn 72938db
docs: Use tabbed group for Docker context config
HofmeisterAn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| namespace DotNet.Testcontainers.Builders | ||
| { | ||
| using System; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Runtime.InteropServices; | ||
| using System.Security.Cryptography; | ||
| using System.Text; | ||
| using System.Text.Json; | ||
| using System.Text.Json.Serialization; | ||
| using DotNet.Testcontainers.Configurations; | ||
| using JetBrains.Annotations; | ||
|
|
||
| /// <summary> | ||
| /// Represents a Docker config. | ||
| /// </summary> | ||
| internal sealed class DockerConfig | ||
| { | ||
| private static readonly string UserProfileDockerConfigDirectoryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".docker"); | ||
|
|
||
| private static readonly string UserProfileDockerContextMetaDirectoryPath = Path.Combine(UserProfileDockerConfigDirectoryPath, "contexts", "meta"); | ||
|
|
||
| private readonly FileInfo _dockerConfigFile; | ||
|
|
||
| private readonly ICustomConfiguration[] _customConfigurations; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="DockerConfig" /> class. | ||
| /// </summary> | ||
| [PublicAPI] | ||
| public DockerConfig() | ||
| : this(GetDockerConfigFile(), EnvironmentConfiguration.Instance, PropertiesFileConfiguration.Instance) | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="DockerConfig" /> class. | ||
| /// </summary> | ||
| /// <param name="customConfigurations">A list of custom configurations.</param> | ||
| [PublicAPI] | ||
| public DockerConfig(params ICustomConfiguration[] customConfigurations) | ||
| : this(GetDockerConfigFile(), customConfigurations) | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="DockerConfig" /> class. | ||
| /// </summary> | ||
| /// <param name="dockerConfigFile">The Docker config file.</param> | ||
| /// <param name="customConfigurations">A list of custom configurations.</param> | ||
| [PublicAPI] | ||
| public DockerConfig(FileInfo dockerConfigFile, params ICustomConfiguration[] customConfigurations) | ||
| { | ||
| _dockerConfigFile = dockerConfigFile; | ||
| _customConfigurations = customConfigurations; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the <see cref="DockerConfig" /> instance. | ||
| /// </summary> | ||
| public static DockerConfig Instance { get; } | ||
| = new DockerConfig(); | ||
|
|
||
| /// <inheritdoc cref="FileInfo.Exists" /> | ||
| public bool Exists => _dockerConfigFile.Exists; | ||
|
|
||
| /// <inheritdoc cref="FileInfo.Exists" /> | ||
| public string FullName => _dockerConfigFile.FullName; | ||
|
|
||
| /// <summary> | ||
| /// Parses the Docker config file. | ||
| /// </summary> | ||
| /// <returns>A <see cref="JsonDocument" /> representing the Docker config.</returns> | ||
| public JsonDocument Parse() | ||
| { | ||
| using (var dockerConfigFileStream = _dockerConfigFile.OpenRead()) | ||
| { | ||
| return JsonDocument.Parse(dockerConfigFileStream); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the current Docker endpoint. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// See the Docker CLI implementation <a href="https://github.com/docker/cli/blob/v25.0.0/cli/command/cli.go#L364-L390">comments</a>. | ||
| /// Executes a command equivalent to <c>docker context inspect --format {{.Endpoints.docker.Host}}</c>. | ||
| /// </remarks> | ||
| /// A <see cref="Uri" /> representing the current Docker endpoint if available; otherwise, <c>null</c>. | ||
| [CanBeNull] | ||
| public Uri GetCurrentEndpoint() | ||
| { | ||
| const string defaultDockerContext = "default"; | ||
|
|
||
| var dockerHost = GetDockerHost(); | ||
| if (dockerHost != null) | ||
| { | ||
| return dockerHost; | ||
| } | ||
|
|
||
| var dockerContext = GetCurrentContext(); | ||
| if (string.IsNullOrEmpty(dockerContext) || defaultDockerContext.Equals(dockerContext)) | ||
| { | ||
| return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? NpipeEndpointAuthenticationProvider.DockerEngine : UnixEndpointAuthenticationProvider.DockerEngine; | ||
| } | ||
|
|
||
| using (var sha256 = SHA256.Create()) | ||
| { | ||
| var dockerContextHash = BitConverter.ToString(sha256.ComputeHash(Encoding.Default.GetBytes(dockerContext))).Replace("-", string.Empty).ToLowerInvariant(); | ||
| var metaFilePath = Path.Combine(UserProfileDockerContextMetaDirectoryPath, dockerContextHash, "meta.json"); | ||
|
|
||
| if (!File.Exists(metaFilePath)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| using (var metaFileStream = File.OpenRead(metaFilePath)) | ||
| { | ||
| var meta = JsonSerializer.Deserialize(metaFileStream, SourceGenerationContext.Default.DockerContextMeta); | ||
| var host = meta?.Name == dockerContext ? meta.Endpoints?.Docker?.Host : null; | ||
| return string.IsNullOrEmpty(host) ? null : new Uri(host.Replace("npipe:////./", "npipe://./")); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [CanBeNull] | ||
| private string GetCurrentContext() | ||
| { | ||
| var dockerContext = GetDockerContext(); | ||
| if (!string.IsNullOrEmpty(dockerContext)) | ||
| { | ||
| return dockerContext; | ||
| } | ||
|
|
||
| if (!Exists) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| using (var dockerConfigJsonDocument = Parse()) | ||
| { | ||
| if (dockerConfigJsonDocument.RootElement.TryGetProperty("currentContext", out var currentContext) && currentContext.ValueKind == JsonValueKind.String) | ||
| { | ||
| return currentContext.GetString(); | ||
| } | ||
| else | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [CanBeNull] | ||
| private Uri GetDockerHost() | ||
| { | ||
| return _customConfigurations.Select(customConfiguration => customConfiguration.GetDockerHost()).FirstOrDefault(dockerHost => dockerHost != null); | ||
| } | ||
|
|
||
| [CanBeNull] | ||
| private string GetDockerContext() | ||
| { | ||
| return _customConfigurations.Select(customConfiguration => customConfiguration.GetDockerContext()).FirstOrDefault(dockerContext => !string.IsNullOrEmpty(dockerContext)); | ||
| } | ||
|
|
||
| private static FileInfo GetDockerConfigFile() | ||
| { | ||
| var dockerConfigDirectoryPath = EnvironmentConfiguration.Instance.GetDockerConfig() ?? PropertiesFileConfiguration.Instance.GetDockerConfig() ?? UserProfileDockerConfigDirectoryPath; | ||
| return new FileInfo(Path.Combine(dockerConfigDirectoryPath, "config.json")); | ||
| } | ||
|
|
||
| internal sealed class DockerContextMeta | ||
| { | ||
| [JsonConstructor] | ||
| public DockerContextMeta(string name, DockerContextMetaEndpoints endpoints) | ||
| { | ||
| Name = name; | ||
| Endpoints = endpoints; | ||
| } | ||
|
|
||
| [JsonPropertyName("Name")] | ||
| public string Name { get; } | ||
|
|
||
| [JsonPropertyName("Endpoints")] | ||
| public DockerContextMetaEndpoints Endpoints { get; } | ||
| } | ||
|
|
||
| internal sealed class DockerContextMetaEndpoints | ||
| { | ||
| [JsonConstructor] | ||
| public DockerContextMetaEndpoints(DockerContextMetaEndpointsDocker docker) | ||
| { | ||
| Docker = docker; | ||
| } | ||
|
|
||
| [JsonPropertyName("docker")] | ||
| public DockerContextMetaEndpointsDocker Docker { get; } | ||
| } | ||
|
|
||
| internal sealed class DockerContextMetaEndpointsDocker | ||
| { | ||
| [JsonConstructor] | ||
| public DockerContextMetaEndpointsDocker(string host) | ||
| { | ||
| Host = host; | ||
| } | ||
|
|
||
| [JsonPropertyName("Host")] | ||
| public string Host { get; } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.