Skip to content

Commit 0d11523

Browse files
committed
storage: Add pkg/supported-digests
- TmpDigestForNewObjects() and TmpSetDigestForNewObjects() for global state - IsSupportedDigestAlgorithm() and GetSupportedDigestAlgorithms() for validation - GetDigestAlgorithmName() with case-insensitive support - GetDigestAlgorithmExpectedLength() for algorithm-to-length mapping - DetectDigestAlgorithmFromLength() for length-based algorithm detection Signed-off-by: Lokesh Mandvekar <[email protected]>
1 parent 840390a commit 0d11523

File tree

2 files changed

+511
-0
lines changed

2 files changed

+511
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package supporteddigests
2+
3+
// Package supporteddigests provides digest algorithm management for container tools.
4+
//
5+
// WARNING: This package is currently Work In Progress (WIP) and is ONLY intended
6+
// for use within Podman, Buildah, and Skopeo. It should NOT be used by external
7+
// applications or libraries, even if shipped in a stable release. The API may
8+
// change without notice and is not considered stable for external consumption.
9+
// Proceed with caution if you must use this package outside of the intended scope.
10+
11+
// FIXME: Use go-digest directly and address all review comments in
12+
// https://github.com/containers/container-libs/pull/374. This is *one* of the blockers
13+
// for removing the WIP warning.
14+
15+
import (
16+
"fmt"
17+
"strings"
18+
"sync"
19+
20+
"github.com/opencontainers/go-digest"
21+
"github.com/sirupsen/logrus"
22+
)
23+
24+
var (
25+
digestAlgorithm = digest.Canonical // Default to SHA256
26+
algorithmMutex sync.RWMutex // Protects digestAlgorithm from concurrent access
27+
)
28+
29+
// TmpDigestForNewObjects returns the current digest algorithm that will be used
30+
// for computing digests of new objects (e.g., image layers, manifests, blobs).
31+
//
32+
// WARNING: This function is part of a WIP package intended only for Podman,
33+
// Buildah, and Skopeo. Do not use in external applications.
34+
//
35+
// This function returns the globally configured digest algorithm for new object
36+
// creation. It is thread-safe and can be called concurrently from multiple
37+
// goroutines using RWMutex. The default value is SHA256 (digest.Canonical) on
38+
// first call.
39+
//
40+
// This is a read-only operation that does not modify global state. The returned
41+
// value reflects the current global configuration set by TmpSetDigestForNewObjects()
42+
// or the default if never set. Multiple concurrent calls will return the same
43+
// algorithm value. The algorithm is used for computing content hashes during
44+
// image operations such as layer extraction, manifest generation, and blob storage.
45+
func TmpDigestForNewObjects() digest.Algorithm {
46+
algorithmMutex.RLock()
47+
defer algorithmMutex.RUnlock()
48+
return digestAlgorithm
49+
}
50+
51+
// TmpSetDigestForNewObjects sets the digest algorithm that will be used for
52+
// computing digests of new objects (e.g., image layers, manifests, blobs).
53+
//
54+
// WARNING: This function is part of a WIP package intended only for Podman,
55+
// Buildah, and Skopeo. Do not use in external applications.
56+
//
57+
// This function configures the globally shared digest algorithm for new object
58+
// creation. It is thread-safe and can be called concurrently from multiple
59+
// goroutines using RWMutex. Changes affect all subsequent calls to
60+
// TmpDigestForNewObjects().
61+
//
62+
// The function validates the algorithm and returns an error for unsupported values.
63+
// Supported algorithms are SHA256, SHA512, or empty string (which defaults to SHA256).
64+
// This is typically used to configure the digest algorithm for the process where
65+
// an optional --digest flag is provided. For example: "podman|buildah build --digest sha512"
66+
// to configure the digest algorithm for the build process.
67+
//
68+
// The setting persists for the lifetime of the process. This is a write operation
69+
// that modifies global state atomically. Invalid algorithms are rejected without
70+
// changing the current setting. Empty string is treated as a request to reset to
71+
// the default (SHA256). Existing digest values are not affected by algorithm changes.
72+
func TmpSetDigestForNewObjects(algorithm digest.Algorithm) error {
73+
algorithmMutex.Lock()
74+
defer algorithmMutex.Unlock()
75+
76+
// Validate the digest type
77+
switch algorithm {
78+
case digest.SHA256, digest.SHA512:
79+
logrus.Debugf("SetDigestAlgorithm: Setting digest algorithm to %s", algorithm.String())
80+
digestAlgorithm = algorithm
81+
return nil
82+
case "":
83+
logrus.Debugf("SetDigestAlgorithm: Setting digest algorithm to default %s", digest.Canonical.String())
84+
digestAlgorithm = digest.Canonical // Default to sha256
85+
return nil
86+
default:
87+
return fmt.Errorf("unsupported digest algorithm: %q", algorithm)
88+
}
89+
}
90+
91+
// IsSupportedDigestAlgorithm checks if the given algorithm is supported by this package.
92+
//
93+
// WARNING: This function is part of a WIP package intended only for Podman,
94+
// Buildah, and Skopeo. Do not use in external applications.
95+
//
96+
// It returns true if the algorithm is explicitly supported (SHA256, SHA512) or if
97+
// it's an empty string or digest.Canonical (both treated as SHA256 default).
98+
// It returns false for any other algorithm including SHA384, MD5, etc.
99+
//
100+
// This is a pure function with no side effects and is thread-safe for concurrent
101+
// calls from multiple goroutines. It is typically used for validation before
102+
// calling TmpSetDigestForNewObjects().
103+
func IsSupportedDigestAlgorithm(algorithm digest.Algorithm) bool {
104+
// Handle special cases first
105+
if algorithm == "" || algorithm == digest.Canonical {
106+
return true // Empty string and canonical are treated as default (SHA256)
107+
}
108+
109+
// Check against the list of supported algorithms
110+
supportedAlgorithms := GetSupportedDigestAlgorithms()
111+
for _, supported := range supportedAlgorithms {
112+
if algorithm == supported {
113+
return true
114+
}
115+
}
116+
return false
117+
}
118+
119+
// GetSupportedDigestAlgorithms returns a list of all supported digest algorithms.
120+
//
121+
// WARNING: This function is part of a WIP package intended only for Podman,
122+
// Buildah, and Skopeo. Do not use in external applications.
123+
//
124+
// It returns a slice containing all algorithms that can be used with
125+
// TmpSetDigestForNewObjects(). Currently returns [SHA256, SHA512].
126+
//
127+
// This is a pure function with no side effects and is thread-safe for concurrent
128+
// calls from multiple goroutines. The returned slice should not be modified by
129+
// callers. It is typically used for validation and algorithm enumeration.
130+
func GetSupportedDigestAlgorithms() []digest.Algorithm {
131+
return []digest.Algorithm{
132+
digest.SHA256,
133+
digest.SHA512,
134+
}
135+
}
136+
137+
// GetDigestAlgorithmName returns a human-readable name for the algorithm.
138+
//
139+
// WARNING: This function is part of a WIP package intended only for Podman,
140+
// Buildah, and Skopeo. Do not use in external applications.
141+
//
142+
// It returns a standardized uppercase name for supported algorithms. The function
143+
// is case-insensitive, so "sha256", "SHA256", "Sha256" all return "SHA256".
144+
// It returns "SHA256 (canonical)" for digest.Canonical and "unknown" for
145+
// unsupported algorithms.
146+
//
147+
// This is a pure function with no side effects and is thread-safe for concurrent
148+
// calls from multiple goroutines. It is typically used for logging and user-facing
149+
// display purposes.
150+
func GetDigestAlgorithmName(algorithm digest.Algorithm) string {
151+
// Normalize to lowercase for case-insensitive matching
152+
normalized := strings.ToLower(algorithm.String())
153+
154+
switch normalized {
155+
case "sha256":
156+
return "SHA256"
157+
case "sha512":
158+
return "SHA512"
159+
default:
160+
if algorithm == digest.Canonical {
161+
return "SHA256 (canonical)"
162+
}
163+
return "unknown"
164+
}
165+
}
166+
167+
// GetDigestAlgorithmExpectedLength returns the expected hex string length for a given algorithm.
168+
//
169+
// WARNING: This function is part of a WIP package intended only for Podman,
170+
// Buildah, and Skopeo. Do not use in external applications.
171+
//
172+
// It returns (length, true) for supported algorithms with known hex lengths.
173+
// SHA256 returns (64, true) and SHA512 returns (128, true). It returns (0, false)
174+
// for unsupported or unknown algorithms. The length represents the number of hex
175+
// characters in the digest string.
176+
//
177+
// This is a pure function with no side effects and is thread-safe for concurrent
178+
// calls from multiple goroutines. It is typically used for validation and algorithm
179+
// detection from hex string lengths.
180+
func GetDigestAlgorithmExpectedLength(algorithm digest.Algorithm) (int, bool) {
181+
switch algorithm {
182+
case digest.SHA256:
183+
return 64, true
184+
case digest.SHA512:
185+
return 128, true
186+
default:
187+
// For future algorithms, this function can be extended
188+
// to support additional algorithms as they are added
189+
return 0, false
190+
}
191+
}
192+
193+
// DetectDigestAlgorithmFromLength attempts to detect the digest algorithm from a hex string length.
194+
//
195+
// WARNING: This function is part of a WIP package intended only for Podman,
196+
// Buildah, and Skopeo. Do not use in external applications.
197+
//
198+
// It returns (algorithm, true) if a supported algorithm matches the given length,
199+
// or (empty, false) if no supported algorithm matches the length. It checks all
200+
// supported algorithms against their expected hex lengths.
201+
//
202+
// This is a pure function with no side effects and is thread-safe for concurrent
203+
// calls from multiple goroutines. It is typically used for reverse lookup when
204+
// only the hex string length is known. Ambiguous lengths (if any) will return
205+
// the first matching algorithm.
206+
func DetectDigestAlgorithmFromLength(length int) (digest.Algorithm, bool) {
207+
for _, algorithm := range GetSupportedDigestAlgorithms() {
208+
if expectedLength, supported := GetDigestAlgorithmExpectedLength(algorithm); supported && expectedLength == length {
209+
return algorithm, true
210+
}
211+
}
212+
return digest.Algorithm(""), false
213+
}

0 commit comments

Comments
 (0)