oci-client Rust Library

repository·main·Indexed 41 days ago

https://github.com/oras-project/rust-oci-client

A Rust implementation of the OCI Distribution specification for interacting with container registries. Version 0.18.0 provides functionality to pull and push OCI images, manifests, and blobs, handle OAuth v2 authentication, and manage cross-repository blob mounts. It supports configurable TLS backends via rustls-tls, rustls-tls-no-provider, and native-tls.

Tokens
13.9K
Snippets
50
Records
68
Agent score
51%

What's inside oci-client

  1. Configure TLS backends for oci-client

    main

    The oci-client crate requires you to enable exactly one of the following three Cargo features for TLS support. Choosing the right one depends on your application's existing crypto requirements and preferred TLS implementation.

    • rustls-tls (default): Uses the rustls library with the built-in aws-lc-rs crypto provider.
    • rustls-tls-no-provider: Uses rustls but requires you to manually install a crypto provider before building a Client. Use this if your application already manages a crypto provider and you want to avoid aws-lc-rs.
    • native-tls: Uses the operating system's native TLS library.
    // Example for using rustls-tls-no-provider with the ring provider
    // You must install the provider before building a Client
    rustls::crypto::ring::default_provider().install_default();
  2. Understand the OCI error format (OciEnvelope and OciError)

    main

    The OCI specification defines a specific JSON error format. The rust-oci-client represents this using OciEnvelope and OciError structs. An OciEnvelope contains a list of OciError objects.

    An OciError consists of:

    • code: An OciErrorCode (e.g., UNAUTHORIZED, MANIFEST_UNKNOWN).
    • message: A human-readable string describing the error.
    • detail: An optional, unstructured serde_json::Value containing additional context.

    If a registry returns an error code not explicitly defined in the OciErrorCode enum, it is captured in the OciErrorCode::Other(String) variant.

    // Example of what an OCI error JSON looks like:
    {
      "errors": [{
        "code": "UNAUTHORIZED",
        "message": "authentication required",
        "detail": [{"Type": "repository", "Name": "hello-wasm", "Action": "pull"}]
      }]
    }
  3. Use OciManifest to handle different manifest types

    main

    The OciManifest enum is an untagged wrapper that can represent either an OciImageManifest (a single image) or an OciImageIndex (a list of manifests for different platforms).

    You can use the .content_type() method to retrieve the appropriate media type for the specific variant, which is useful when interacting with OCI registries.

    • OciManifest::Image(OciImageManifest): Represents a single OCI image.
    • OciManifest::ImageIndex(OciImageIndex): Represents an image index (manifest list).
    // Example of getting the content type from an OciManifest
    let content_type = manifest.content_type();
  4. Customize image selection with `PlatformResolverFn`

    main

    When pulling an image that is actually an OCI Image Index (a manifest list), the client needs to know which specific variant (OS/Arch) to select. You can provide a custom PlatformResolverFn in your ClientConfig to control this behavior.

    Built-in resolvers include:

    • current_platform_resolver: Matches the running OS and Architecture.
    • linux_amd64_resolver: Specifically selects the first linux/amd64 variant.
    • windows_amd64_resolver: Specifically selects the first windows/amd64 variant.
    // Example: Using a custom resolver in ClientConfig
    let config = ClientConfig {
        platform_resolver: Some(Box::new(|manifests| {
            // Custom logic to pick a digest from the manifests
            manifests.iter().find(|m| m.platform.as_ref().map_or(false, |p| p.os == "linux"))
                .map(|m| m.digest.clone())
        })),
        ..Default::default()
    };
  5. Manage OCI registry authentication tokens with TokenCache

    main

    The TokenCache provides an asynchronous mechanism to store and retrieve authentication tokens for OCI registries. It caches tokens based on a combination of the registry URL, the repository path, and the intended operation (Push or Pull). This prevents redundant authentication requests by checking the token's expiration before returning it.

    Key Concepts

    • RegistryOperation: Defines the intent of the request. Use RegistryOperation::Push for uploading and RegistryOperation::Pull for downloading.
    • RegistryTokenType: Represents the authentication method. It can be Bearer(RegistryToken) for OAuth2-style tokens or Basic(String, String) for username/password credentials.
    • Expiration Logic: For Bearer tokens, the cache attempts to read the exp claim from the JWT payload to determine lifetime. If the token is opaque (like those from GHCR) or lacks an exp claim, it falls back to a default_expiration_secs value. To prevent malicious or far-future tokens from pinning the cache, the TTL is capped at 24 hours (MAX_TOKEN_CACHE_TTL_SECS).
    // Example conceptual usage of TokenCache
    let cache = TokenCache::new(3600); // 1 hour default expiration
    let reference = "registry.example.com/repo/image:tag".parse::<Reference>().unwrap();
    
    // Inserting a token
    let token = RegistryTokenType::Bearer(RegistryToken::Token { 
        token: "my_token_string".to_string() 
    });
    cache.insert(&reference, RegistryOperation::Pull, token).await;
    
    // Retrieving a token
    if let Some(cached_token) = cache.get(&reference, RegistryOperation::Pull).await {
        // Use the token...
    }
  6. Configure TLS certificates and protocols

    main

    The client supports custom TLS configurations via ClientConfig and the Certificate struct.

    Certificate Formats

    Use the Certificate struct to provide certificates in either Der or Pem encoding:

    let cert = Certificate {
        encoding: CertificateEncoding::Pem,
        data: vec![...],
    };

    Protocol Selection

    Control whether the client uses HTTP or HTTPS via ClientProtocol:

    • ClientProtocol::Http: Always use HTTP.
    • ClientProtocol::Https: Always use HTTPS.
    • ClientProtocol::HttpsExcept(Vec<String>): Use HTTPS for all registries except those listed in the provided vector.
  7. Handle OCI blob responses with BlobResponse

    main

    When requesting blobs from an OCI registry, the response is encapsulated in the BlobResponse enum. This distinguishes between a full blob download and a partial blob download (used for range requests).

    • BlobResponse::Full(SizedStream): The response contains the complete blob.
    • BlobResponse::Partial(SizedStream): The response contains only a portion of the blob as requested.
  8. Initialize the OCI Client

    main

    To interact with an OCI registry, create a Client instance. You can create a client using a ClientConfig or by implementing the ClientConfigSource trait on your own configuration type.

    If you use Client::new(config), it attempts to build the client from the provided configuration. If the configuration is invalid, it falls back to a default configuration and logs a warning.

    // Using ClientConfig directly
    let config = ClientConfig { ... };
    let client = Client::new(config);
    
    // Using a custom source via ClientConfigSource
    struct MyConfig; 
    impl ClientConfigSource for MyConfig {
        fn client_config(&self) -> ClientConfig {
            // return your config
        }
    }
    let client = Client::from_source(&MyConfig);
  9. Configure the OCI Client with `ClientConfig`

    main

    The ClientConfig struct allows fine-grained control over the client's behavior. Key configuration options include:

    • protocol: Set to ClientProtocol::Http, ClientProtocol::Https, or ClientProtocol::HttpsExcept(Vec<String>) to control connection security.
    • accept_invalid_certificates: Boolean to allow connections to registries with invalid TLS certificates.
    • extra_root_certificates: A Vec<Certificate> of additional root certificates to trust (useful for self-signed certs).
    • tls_certs_only: If provided, the client will only use these certificates, disabling all native/built-in roots.
    • platform_resolver: A function to decide which image variant to pull when an Image Index is encountered. Defaults to current_platform_resolver.
    • max_concurrent_upload / max_concurrent_download: Limits for parallel operations.
    • read_timeout / connect_timeout: Network timeout settings.
    • https_proxy / http_proxy / no_proxy: Proxy configuration.
  10. Push a blob using chunked sessions

    main

    For large blobs, you can perform a chunked upload by initiating a session, pushing individual chunks, and then finalizing the session. This is useful for managing memory and handling large data transfers in stages.

    1. Call begin_push_chunked_session to get a session location.
    2. Call push_chunk repeatedly with the session location and the data chunk.
    3. Call end_push_chunked_session with the final location and the total digest to complete the upload.
    let location = c
        .begin_push_chunked_session(&image)
        .await
        .expect("failed to begin push session");
    
    let image_data = Bytes::from(b"iamawebassemblymodule".to_vec());
    let (next_location, next_byte) = c
        .push_chunk(&location, &image, image_data.clone(), 0)
        .await
        .expect("failed to push layer");
    
    let layer_location = c
        .end_push_chunked_session(&next_location, &image, &sha256_digest(&image_data))
        .await
        .expect("failed to end push session");
  11. Describe layer history with `History`

    main

    The History struct describes the history of each layer in the image. The array of history items is ordered from first to last.

    Fields:

    • created: Timestamp of layer creation (RFC 3339).
    • author: The author of the build point.
    • created_by: The command which created the layer.
    • comment: A custom message set when creating the layer.
    • empty_layer: A boolean indicating if this history item created a filesystem diff. If true, this item does not correspond to an actual layer in the rootfs section (e.g., a Dockerfile ENV command).