/** * Extract an {@link ErrorCodes} response from the error object encoded in an {@link * HttpResponseException}. * * @param httpResponseException the response exception * @return the parsed {@link ErrorCodes} if found * @throws HttpResponseException rethrows the original exception if an error object could not be * parsed, if there were multiple error objects, or if the error code is unknown. */ public static async Task <ErrorCode> GetErrorCodeAsync(HttpResponseMessage httpResponse) { httpResponse = httpResponse ?? throw new ArgumentNullException(nameof(httpResponse)); // Obtain the error response code. string errorContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); if (errorContent == null) { throw new HttpResponseException(httpResponse); } try { ErrorResponseTemplate errorResponse = JsonTemplateMapper.ReadJson <ErrorResponseTemplate>(errorContent); IReadOnlyList <ErrorEntryTemplate> errors = errorResponse?.Errors; // There may be multiple error objects if (errors?.Count == 1) { var errorCode = errors[0].Code; // May not get an error code back. if (errorCode.HasValue) { return(errorCode.GetValueOrDefault()); } } } catch (Exception e) when(e is IOException || e is ArgumentException) { // Parse exception: either isn't an error object or unknown error code } // rethrow the original exception throw new HttpResponseException(httpResponse); }
protected override IManifestTemplate GetManifestTemplateFromJson(string jsonString) { var token = JToken.Parse(jsonString); if (!(token is JObject obj)) { throw new UnknownManifestFormatException(Resources.ManifestPullerNotJsonExceptionMessage); } if (!obj.ContainsKey("schemaVersion")) { throw new UnknownManifestFormatException(Resources.ManifestPullerMissingSchemaVersionExceptionMessage); } if (!obj.TryGetValue("schemaVersion", out JToken schemaVersionToken) || schemaVersionToken.Type != JTokenType.Integer) { throw new UnknownManifestFormatException(Resources.ManifestPullerSchemaVersionNotIntExceptionMessage); } int schemaVersion = schemaVersionToken.Value <int>(); if (schemaVersion == 1) { return(JsonTemplateMapper.ReadJson <V21ManifestTemplate>(jsonString)); } if (schemaVersion == 2) { // 'schemaVersion' of 2 can be either Docker V2.2 or OCI. string mediaType = obj.Value <string>("mediaType"); if (V22ManifestTemplate.ManifestMediaType == mediaType) { return(JsonTemplateMapper.ReadJson <V22ManifestTemplate>(jsonString)); } if (OCIManifestTemplate.ManifestMediaType == mediaType) { return(JsonTemplateMapper.ReadJson <OCIManifestTemplate>(jsonString)); } throw new UnknownManifestFormatException("Unknown mediaType: " + mediaType); } throw new UnknownManifestFormatException( "Unknown schemaVersion: " + schemaVersion + " - only 1 and 2 are supported"); }
/** * Attempts to parse the container configuration JSON (of format {@code * application/vnd.docker.container.image.v1+json}) from the {@code v1Compatibility} value of the * first {@code history} entry, which corresponds to the latest layer. * * @return container configuration if the first history string holds it; {@code null} otherwise */ public Maybe <ContainerConfigurationTemplate> GetContainerConfiguration() { try { if (History.Count == 0) { return(Maybe.Empty <ContainerConfigurationTemplate>()); } string v1Compatibility = History[0].V1Compatibility; if (v1Compatibility == null) { return(Maybe.Empty <ContainerConfigurationTemplate>()); } return(Maybe.Of( JsonTemplateMapper.ReadJson <ContainerConfigurationTemplate>(v1Compatibility))); } catch (IOException) { // not a container configuration; ignore and continue return(Maybe.Empty <ContainerConfigurationTemplate>()); } }
/** * Pulls the base image. * * @param registryAuthorization authentication credentials to possibly use * @param progressEventDispatcher the {@link ProgressEventDispatcher} for emitting {@link * ProgressEvent}s * @return the pulled image * @throws IOException when an I/O exception occurs during the pulling * @throws RegistryException if communicating with the registry caused a known error * @throws LayerCountMismatchException if the manifest and configuration contain conflicting layer * information * @throws LayerPropertyNotFoundException if adding image layers fails * @throws BadContainerConfigurationFormatException if the container configuration is in a bad * format */ private async Task <Image> PullBaseImageAsync( Authorization registryAuthorization, ProgressEventDispatcher progressEventDispatcher) { RegistryClient registryClient = buildConfiguration .NewBaseImageRegistryClientFactory() .SetAuthorization(registryAuthorization) .NewRegistryClient(); IManifestTemplate manifestTemplate = await registryClient.PullManifestAsync(buildConfiguration.GetBaseImageConfiguration().GetImageTag()).ConfigureAwait(false); // TODO: Make schema version be enum. switch (manifestTemplate.SchemaVersion) { case 1: V21ManifestTemplate v21ManifestTemplate = (V21ManifestTemplate)manifestTemplate; await buildConfiguration .GetBaseImageLayersCache() .WriteMetadataAsync( buildConfiguration.GetBaseImageConfiguration().GetImage(), v21ManifestTemplate).ConfigureAwait(false); return(JsonToImageTranslator.ToImage(v21ManifestTemplate)); case 2: IBuildableManifestTemplate buildableManifestTemplate = (IBuildableManifestTemplate)manifestTemplate; if (buildableManifestTemplate.GetContainerConfiguration() == null || buildableManifestTemplate.GetContainerConfiguration().Digest == null) { throw new UnknownManifestFormatException( "Invalid container configuration in Docker V2.2/OCI manifest: \n" + JsonTemplateMapper.ToUtf8String(buildableManifestTemplate)); } DescriptorDigest containerConfigurationDigest = buildableManifestTemplate.GetContainerConfiguration().Digest; using (ThrottledProgressEventDispatcherWrapper progressEventDispatcherWrapper = new ThrottledProgressEventDispatcherWrapper( progressEventDispatcher.NewChildProducer(), "pull container configuration " + containerConfigurationDigest)) { string containerConfigurationString = await Blobs.WriteToStringAsync( registryClient.PullBlob( containerConfigurationDigest, progressEventDispatcherWrapper.SetProgressTarget, progressEventDispatcherWrapper.DispatchProgress)).ConfigureAwait(false); ContainerConfigurationTemplate containerConfigurationTemplate = JsonTemplateMapper.ReadJson <ContainerConfigurationTemplate>( containerConfigurationString); await buildConfiguration .GetBaseImageLayersCache() .WriteMetadataAsync( buildConfiguration.GetBaseImageConfiguration().GetImage(), buildableManifestTemplate, containerConfigurationTemplate).ConfigureAwait(false); return(JsonToImageTranslator.ToImage( buildableManifestTemplate, containerConfigurationTemplate)); } } throw new InvalidOperationException(Resources.PullBaseImageStepUnknownManifestErrorMessage); }
/** * Calls the credential helper CLI in the form: * * <pre>{@code * echo -n <server Uri> | docker-credential-<credential helper suffix> get * }</pre> * * @return the Docker credentials by calling the corresponding CLI * @throws IOException if writing/reading process input/output fails * @throws CredentialHelperUnhandledServerUrlException if no credentials could be found for the * corresponding server * @throws CredentialHelperNotFoundException if the credential helper CLI doesn't exist */ public Credential Retrieve() { try { IProcess process = new ProcessBuilder(credentialHelper, "get").Start(); using (Stream processStdin = process.GetOutputStream()) { byte[] bytes = Encoding.UTF8.GetBytes(registry); processStdin.Write(bytes, 0, bytes.Length); } using (StreamReader processStdoutReader = new StreamReader(process.GetInputStream(), Encoding.UTF8)) { string output = processStdoutReader.ReadToEnd(); // Throws an exception if the credential store does not have credentials for serverUrl. if (output.Contains("credentials not found in native keychain")) { throw new CredentialHelperUnhandledServerUrlException( credentialHelper, registry, output); } if (string.IsNullOrEmpty(output)) { ThrowUnhandledUrlException(process); } try { DockerCredentialsTemplate dockerCredentials = JsonTemplateMapper.ReadJson <DockerCredentialsTemplate>(output); if (string.IsNullOrEmpty(dockerCredentials.Username) || string.IsNullOrEmpty(dockerCredentials.Secret)) { throw new CredentialHelperUnhandledServerUrlException( credentialHelper, registry, output); } return(Credential.From(dockerCredentials.Username, dockerCredentials.Secret)); } catch (JsonException ex) { throw new CredentialHelperUnhandledServerUrlException( credentialHelper, registry, output, ex); } } } catch (Win32Exception ex) when(ex.NativeErrorCode == Win32ErrorCodes.FileNotFound) { if (ex.Message == null) { throw; } // Checks if the failure is due to a nonexistent credential helper CLI. if (ex.Message.Contains("No such file or directory") || ex.Message.Contains("cannot find the file")) { throw new CredentialHelperNotFoundException(credentialHelper, ex); } throw; } }
/** * Sends the authentication request and retrieves the Bearer authorization token. * * @param credential the credential used to authenticate * @param scope the scope of permissions to authenticate for * @return the {@link Authorization} response * @throws RegistryAuthenticationFailedException if authentication fails * @see <a * href="https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate">https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate</a> */ private async Task <Authorization> AuthenticateAsync(Credential credential, string scope) { try { using (Connection connection = Connection.GetConnectionFactory()(GetAuthenticationUrl(credential, scope))) using (var request = new HttpRequestMessage()) { foreach (var value in userAgent) { request.Headers.UserAgent.Add(value); } if (IsOAuth2Auth(credential)) { string parameters = GetAuthRequestParameters(credential, scope); request.Content = new BlobHttpContent(Blobs.From(parameters), MediaType.FormData); } else if (credential != null) { Authorization authorization = Authorization.FromBasicCredentials(credential.GetUsername(), credential.GetPassword()); request.Headers.Authorization = new AuthenticationHeaderValue(authorization.GetScheme(), authorization.GetToken()); } if (IsOAuth2Auth(credential)) { request.Method = HttpMethod.Post; } else { request.Method = HttpMethod.Get; } string responseString; using (HttpResponseMessage response = await connection.SendAsync(request).ConfigureAwait(false)) using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), Encoding.UTF8)) { responseString = await reader.ReadToEndAsync().ConfigureAwait(false); } AuthenticationResponseTemplate responseJson = JsonTemplateMapper.ReadJson <AuthenticationResponseTemplate>(responseString); if (responseJson.GetTokenOrAccessToken() == null) { throw new RegistryAuthenticationFailedException( registryEndpointRequestProperties.GetRegistry(), registryEndpointRequestProperties.GetImageName(), "Did not get token in authentication response from " + GetAuthenticationUrl(credential, scope) + "; parameters: " + GetAuthRequestParameters(credential, scope)); } return(Authorization.FromBearerToken(responseJson.GetTokenOrAccessToken())); } } catch (Exception ex) when(ex is IOException || ex is JsonException) { throw new RegistryAuthenticationFailedException( registryEndpointRequestProperties.GetRegistry(), registryEndpointRequestProperties.GetImageName(), ex); } }
public async Task TestWriteToAsync() { SystemPath fileA = Paths.Get(TestResources.GetResource("core/fileA").ToURI()); SystemPath fileB = Paths.Get(TestResources.GetResource("core/fileB").ToURI()); long fileASize = Files.Size(fileA); long fileBSize = Files.Size(fileB); DescriptorDigest fakeDigestA = DescriptorDigest.FromHash( "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"); DescriptorDigest fakeDigestB = DescriptorDigest.FromHash( "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc6"); Mock.Get(mockLayer1).Setup(m => m.GetBlob()).Returns(Blobs.From(fileA)); Mock.Get(mockLayer1).Setup(m => m.GetBlobDescriptor()).Returns(new BlobDescriptor(fileASize, fakeDigestA)); Mock.Get(mockLayer1).Setup(m => m.GetDiffId()).Returns(fakeDigestA); Mock.Get(mockLayer2).Setup(m => m.GetBlob()).Returns(Blobs.From(fileB)); Mock.Get(mockLayer2).Setup(m => m.GetBlobDescriptor()).Returns(new BlobDescriptor(fileBSize, fakeDigestB)); Mock.Get(mockLayer2).Setup(m => m.GetDiffId()).Returns(fakeDigestB); Image testImage = Image.CreateBuilder(ManifestFormat.V22).AddLayer(mockLayer1).AddLayer(mockLayer2).Build(); ImageTarball imageToTarball = new ImageTarball(testImage, ImageReference.Parse("my/image:tag")); MemoryStream @out = new MemoryStream(); await imageToTarball.WriteToAsync(@out).ConfigureAwait(false); MemoryStream @in = new MemoryStream(@out.ToArray()); using (TarInputStream tarArchiveInputStream = new TarInputStream(@in)) { // Verifies layer with fileA was added. TarEntry headerFileALayer = tarArchiveInputStream.GetNextEntry(); Assert.AreEqual(fakeDigestA.GetHash() + ".tar.gz", headerFileALayer.Name); string fileAString = await new StreamReader(tarArchiveInputStream).ReadToEndAsync().ConfigureAwait(false); Assert.AreEqual(await Blobs.WriteToStringAsync(Blobs.From(fileA)).ConfigureAwait(false), fileAString); // Verifies layer with fileB was added. TarEntry headerFileBLayer = tarArchiveInputStream.GetNextEntry(); Assert.AreEqual(fakeDigestB.GetHash() + ".tar.gz", headerFileBLayer.Name); string fileBString = await new StreamReader(tarArchiveInputStream).ReadToEndAsync().ConfigureAwait(false); Assert.AreEqual(await Blobs.WriteToStringAsync(Blobs.From(fileB)).ConfigureAwait(false), fileBString); // Verifies container configuration was added. TarEntry headerContainerConfiguration = tarArchiveInputStream.GetNextEntry(); Assert.AreEqual("config.json", headerContainerConfiguration.Name); string containerConfigJson = await new StreamReader(tarArchiveInputStream).ReadToEndAsync().ConfigureAwait(false); JsonTemplateMapper.ReadJson <ContainerConfigurationTemplate>(containerConfigJson); // Verifies manifest was added. TarEntry headerManifest = tarArchiveInputStream.GetNextEntry(); Assert.AreEqual("manifest.json", headerManifest.Name); string manifestJson = await new StreamReader(tarArchiveInputStream).ReadToEndAsync().ConfigureAwait(false); JsonTemplateMapper.ReadListOfJson <DockerLoadManifestEntryTemplate>(manifestJson); } }