Skip to content

Commit 648c916

Browse files
authored
Add layout ref to access descriptor on OCI layout (#202)
2 parents 526739b + fabca21 commit 648c916

22 files changed

+487
-82
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ target/
3232
# When testing JSON files
3333
*.json
3434
oci/
35+
!src/test/resources/oci/
36+
!src/test/resources/oci/**/*.json

.pre-commit-config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ repos:
44
hooks:
55
- id: end-of-file-fixer
66
- id: trailing-whitespace
7+
exclude: ^src/test/resources/oci/artifact/

pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>land.oras</groupId>
66
<artifactId>oras-java-sdk</artifactId>
7-
<version>0.2.1-SNAPSHOT</version>
7+
<version>0.3.0-SNAPSHOT</version>
88
<packaging>jar</packaging>
99
<name>${project.groupId}:${project.artifactId}</name>
1010
<description>ORAS Java SDK</description>
@@ -253,7 +253,7 @@
253253
<inceptionYear>2024</inceptionYear>
254254
<addJavaLicenseAfterPackage>false</addJavaLicenseAfterPackage>
255255
<emptyLineAfterHeader>true</emptyLineAfterHeader>
256-
<excludes>**/logback*.xml,**/junit-platform.properties</excludes>
256+
<excludes>**/logback*.xml,**/junit-platform.properties,**/*.json</excludes>
257257
<processStartTag>= LICENSE =</processStartTag>
258258
<sectionDelimiter>===</sectionDelimiter>
259259
<processEndTag>= LICENSE END =</processEndTag>

src/main/java/land/oras/ContainerRef.java

+2-15
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* A referer of a container on a {@link Registry}.
3535
*/
3636
@NullMarked
37-
public final class ContainerRef {
37+
public final class ContainerRef extends Ref {
3838

3939
/**
4040
* The regex pattern to parse the container name including the registry, namespace, repository, tag and digest.
@@ -62,11 +62,6 @@ public final class ContainerRef {
6262
*/
6363
private final @Nullable String namespace;
6464

65-
/**
66-
* The tag of the container.
67-
*/
68-
private final String tag;
69-
7065
/**
7166
* The digest of the container.
7267
*/
@@ -82,10 +77,10 @@ public final class ContainerRef {
8277
*/
8378
private ContainerRef(
8479
String registry, @Nullable String namespace, String repository, String tag, @Nullable String digest) {
80+
super(tag);
8581
this.registry = registry;
8682
this.namespace = namespace;
8783
this.repository = repository;
88-
this.tag = tag;
8984
this.digest = digest;
9085
}
9186

@@ -129,14 +124,6 @@ public String getRepository() {
129124
return repository;
130125
}
131126

132-
/**
133-
* Get the tag
134-
* @return The tag
135-
*/
136-
public String getTag() {
137-
return tag;
138-
}
139-
140127
/**
141128
* Get the digest
142129
* @return The digest

src/main/java/land/oras/Index.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package land.oras;
2222

23+
import java.nio.file.Path;
2324
import java.util.List;
2425
import land.oras.utils.Const;
2526
import land.oras.utils.JsonUtils;
@@ -135,14 +136,23 @@ public String getJson() {
135136
}
136137

137138
/**
138-
* Create a manifest from a JSON string
139+
* Create an index from a JSON string
139140
* @param json The JSON string
140141
* @return The index
141142
*/
142143
public static Index fromJson(String json) {
143144
return JsonUtils.fromJson(json, Index.class).withJson(json);
144145
}
145146

147+
/**
148+
* Create an index from a path
149+
* @param path The path
150+
* @return The index
151+
*/
152+
public static Index fromPath(Path path) {
153+
return JsonUtils.fromJson(path, Index.class);
154+
}
155+
146156
/**
147157
* Create an index from a list of manifests
148158
* @param descriptors The list of manifests
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*-
2+
* =LICENSE=
3+
* ORAS Java SDK
4+
* ===
5+
* Copyright (C) 2024 - 2025 ORAS
6+
* ===
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* =LICENSEEND=
19+
*/
20+
21+
package land.oras;
22+
23+
import java.nio.file.Path;
24+
import java.util.regex.Pattern;
25+
import land.oras.exception.OrasException;
26+
import org.jspecify.annotations.NullMarked;
27+
28+
/**
29+
* A referer of a container on a {@link OCILayout}.
30+
*/
31+
@NullMarked
32+
public final class LayoutRef extends Ref {
33+
34+
private final Path folder;
35+
36+
private static final Pattern NAME_REGEX = Pattern.compile(
37+
"^(.+?)(?::([^:@]+))?(?:@(.+))?$" // folder[:tag][@digest]
38+
);
39+
40+
/**
41+
* Private constructor
42+
* @param tag The tag.
43+
*/
44+
private LayoutRef(Path folder, String tag) {
45+
super(tag);
46+
this.folder = folder;
47+
}
48+
49+
/**
50+
* Get the folder
51+
* @return The folder
52+
*/
53+
public Path getFolder() {
54+
return folder;
55+
}
56+
57+
/**
58+
* Parse the layout ref with folder and tag.
59+
* @param name The layout ref.
60+
* @return The container object with the registry, repository and tag.
61+
*/
62+
public static LayoutRef parse(String name) {
63+
var matcher = NAME_REGEX.matcher(name);
64+
if (!matcher.matches()) {
65+
throw new OrasException("Invalid layout ref: " + name);
66+
}
67+
Path path = Path.of(matcher.group(1)); // Folder path
68+
String tag = matcher.group(2) != null ? matcher.group(2) : matcher.group(3); // Tag or digest
69+
return new LayoutRef(path, tag);
70+
}
71+
}

src/main/java/land/oras/Manifest.java

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package land.oras;
2222

23+
import java.nio.file.Path;
2324
import java.util.Collections;
2425
import java.util.List;
2526
import java.util.Map;
@@ -253,6 +254,15 @@ public static Manifest fromJson(String json) {
253254
return JsonUtils.fromJson(json, Manifest.class).withJson(json);
254255
}
255256

257+
/**
258+
* Create a manifest from a path
259+
* @param path The path
260+
* @return The manifest
261+
*/
262+
public static Manifest fromPath(Path path) {
263+
return JsonUtils.fromJson(path, Manifest.class);
264+
}
265+
256266
/**
257267
* Return the original JSON
258268
* @return The original JSON

src/main/java/land/oras/OCI.java

+82-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,93 @@
2020

2121
package land.oras;
2222

23+
import java.io.InputStream;
24+
import java.nio.file.Path;
25+
import org.jspecify.annotations.Nullable;
26+
2327
/**
2428
* Abstract class for OCI operation on remote registry or layout
29+
* Commons methods for OCI operations
30+
* @param <T> The reference type
2531
*/
26-
public abstract sealed class OCI permits Registry, OCILayout {
32+
public abstract sealed class OCI<T extends Ref> permits Registry, OCILayout {
2733

2834
/**
2935
* Default constructor
3036
*/
31-
public OCI() {}
37+
protected OCI() {}
38+
39+
/**
40+
* Push an artifact
41+
* @param ref The ref
42+
* @param paths The paths
43+
* @return The manifest
44+
*/
45+
public Manifest pushArtifact(T ref, LocalPath... paths) {
46+
return pushArtifact(ref, ArtifactType.unknown(), Annotations.empty(), Config.empty(), paths);
47+
}
48+
49+
/**
50+
* Push an artifact
51+
* @param ref The ref
52+
* @param artifactType The artifact type
53+
* @param paths The paths
54+
* @return The manifest
55+
*/
56+
public Manifest pushArtifact(T ref, ArtifactType artifactType, LocalPath... paths) {
57+
return pushArtifact(ref, artifactType, Annotations.empty(), Config.empty(), paths);
58+
}
59+
60+
/**
61+
* Upload an ORAS artifact
62+
* @param ref The ref
63+
* @param artifactType The artifact type
64+
* @param annotations The annotations
65+
* @param paths The paths
66+
* @return The manifest
67+
*/
68+
public Manifest pushArtifact(T ref, ArtifactType artifactType, Annotations annotations, LocalPath... paths) {
69+
return pushArtifact(ref, artifactType, annotations, Config.empty(), paths);
70+
}
71+
72+
/**
73+
* Push an artifact
74+
* @param ref The container
75+
* @param artifactType The artifact type. Can be null
76+
* @param annotations The annotations
77+
* @param config The config
78+
* @param paths The paths
79+
* @return The manifest
80+
*/
81+
public abstract Manifest pushArtifact(
82+
T ref, ArtifactType artifactType, Annotations annotations, @Nullable Config config, LocalPath... paths);
83+
84+
/**
85+
* Pull an artifact
86+
* @param ref The reference of the artifact
87+
* @param path The path to save the artifact
88+
* @param overwrite Overwrite the artifact if it exists
89+
*/
90+
public abstract void pullArtifact(T ref, Path path, boolean overwrite);
91+
92+
/**
93+
* Get the blob for the given digest. Not be suitable for large blobs
94+
* @param ref The ref
95+
* @return The blob as bytes
96+
*/
97+
public abstract byte[] getBlob(T ref);
98+
99+
/**
100+
* Fetch blob and save it to file
101+
* @param ref The ref
102+
* @param path The path to save the blob
103+
*/
104+
public abstract void fetchBlob(T ref, Path path);
105+
106+
/**
107+
* Fetch blob and return it as input stream
108+
* @param ref The ref
109+
* @return The input stream
110+
*/
111+
public abstract InputStream fetchBlob(T ref);
32112
}

0 commit comments

Comments
 (0)