diff --git a/app/build.gradle b/app/build.gradle index c37b240..ba43057 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,7 +21,7 @@ repositories { } dependencies { - implementation 'io.seqera:wave-api:0.8.0' + implementation 'io.seqera:wave-api:0.9.0' implementation 'io.seqera:wave-utils:0.12.0' implementation 'info.picocli:picocli:4.6.1' implementation 'com.squareup.moshi:moshi:1.15.0' diff --git a/app/conf/reflect-config.json b/app/conf/reflect-config.json index 6a13998..29f267c 100644 --- a/app/conf/reflect-config.json +++ b/app/conf/reflect-config.json @@ -239,6 +239,18 @@ "name":"io.seqera.wave.cli.json.PathAdapter", "queryAllDeclaredMethods":true }, +{ + "name":"io.seqera.wave.cli.model.ContainerInspectResponseEx", + "allDeclaredFields":true +}, +{ + "name":"io.seqera.wave.cli.model.ContainerSpecEx", + "allDeclaredFields":true +}, +{ + "name":"io.seqera.wave.cli.model.LayerRef", + "allDeclaredFields":true +}, { "name":"io.seqera.wave.cli.util.CliVersionProvider", "allDeclaredFields":true, diff --git a/app/src/main/java/io/seqera/wave/cli/App.java b/app/src/main/java/io/seqera/wave/cli/App.java index b4adb73..19ca446 100644 --- a/app/src/main/java/io/seqera/wave/cli/App.java +++ b/app/src/main/java/io/seqera/wave/cli/App.java @@ -51,6 +51,8 @@ import io.seqera.wave.cli.exception.ClientConnectionException; import io.seqera.wave.cli.exception.IllegalCliArgumentException; import io.seqera.wave.cli.json.JsonHelper; +import io.seqera.wave.cli.model.ContainerInspectResponseEx; +import io.seqera.wave.cli.model.ContainerSpecEx; import io.seqera.wave.cli.util.BuildInfo; import io.seqera.wave.cli.util.CliVersionProvider; import io.seqera.wave.cli.util.YamlHelper; @@ -409,7 +411,8 @@ public void inspect() { ; final ContainerInspectResponse resp = client.inspect(req); - System.out.println(dumpOutput(resp)); + final ContainerSpecEx spec = new ContainerSpecEx(resp.getContainer()); + System.out.println(dumpOutput(new ContainerInspectResponseEx(spec))); } @Override @@ -629,7 +632,7 @@ protected String dumpOutput(SubmitContainerTokenResponse resp) { return resp.targetImage; } - protected String dumpOutput(ContainerInspectResponse resp) { + protected String dumpOutput(ContainerInspectResponseEx resp) { if( "json".equals(outputFormat) || outputFormat==null ) { return JsonHelper.toJson(resp); } diff --git a/app/src/main/java/io/seqera/wave/cli/json/JsonHelper.java b/app/src/main/java/io/seqera/wave/cli/json/JsonHelper.java index 2cba8fb..60b9183 100644 --- a/app/src/main/java/io/seqera/wave/cli/json/JsonHelper.java +++ b/app/src/main/java/io/seqera/wave/cli/json/JsonHelper.java @@ -22,9 +22,9 @@ import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import io.seqera.wave.api.ContainerInspectRequest; -import io.seqera.wave.api.ContainerInspectResponse; import io.seqera.wave.api.SubmitContainerTokenRequest; import io.seqera.wave.api.SubmitContainerTokenResponse; +import io.seqera.wave.cli.model.ContainerInspectResponseEx; /** * Helper class to encode and decode JSON payloads @@ -54,8 +54,8 @@ public static String toJson(ContainerInspectRequest request) { return adapter.toJson(request); } - public static String toJson(ContainerInspectResponse response) { - JsonAdapter adapter = moshi.adapter(ContainerInspectResponse.class); + public static String toJson(ContainerInspectResponseEx response) { + JsonAdapter adapter = moshi.adapter(ContainerInspectResponseEx.class); return adapter.toJson(response); } diff --git a/app/src/main/java/io/seqera/wave/cli/model/ContainerInspectResponseEx.java b/app/src/main/java/io/seqera/wave/cli/model/ContainerInspectResponseEx.java new file mode 100644 index 0000000..446c210 --- /dev/null +++ b/app/src/main/java/io/seqera/wave/cli/model/ContainerInspectResponseEx.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.seqera.wave.cli.model; + +import io.seqera.wave.api.ContainerInspectResponse; +import io.seqera.wave.core.spec.ContainerSpec; + +/** + * @author Paolo Di Tommaso + */ +public class ContainerInspectResponseEx extends ContainerInspectResponse { + + public ContainerInspectResponseEx(ContainerInspectResponse response) { + super(new ContainerSpecEx(response.getContainer())); + } + + public ContainerInspectResponseEx(ContainerSpec spec) { + super(new ContainerSpecEx(spec)); + } +} diff --git a/app/src/main/java/io/seqera/wave/cli/model/ContainerSpecEx.java b/app/src/main/java/io/seqera/wave/cli/model/ContainerSpecEx.java new file mode 100644 index 0000000..27bf60e --- /dev/null +++ b/app/src/main/java/io/seqera/wave/cli/model/ContainerSpecEx.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.seqera.wave.cli.model; + +import java.util.List; + +import io.seqera.wave.core.spec.ContainerSpec; +import io.seqera.wave.core.spec.ObjectRef; + +/** + * Wrapper for {@link ContainerSpec} that replaces + * {@link ObjectRef} with {@link LayerRef} objects + * + * @author Paolo Di Tommaso + */ +public class ContainerSpecEx extends ContainerSpec { + public ContainerSpecEx(ContainerSpec spec) { + super(spec); + // update the layers uri + if( spec.getManifest()!=null && spec.getManifest().getLayers()!=null ) { + List layers = spec.getManifest().getLayers(); + for( int i=0; i + */ +public class LayerRef extends ObjectRef { + + final public String uri; + + public LayerRef(ObjectRef obj, String uri) { + super(obj); + this.uri = uri; + } + +} diff --git a/app/src/main/java/io/seqera/wave/cli/util/YamlHelper.java b/app/src/main/java/io/seqera/wave/cli/util/YamlHelper.java index d7c7eeb..9a5b8ef 100644 --- a/app/src/main/java/io/seqera/wave/cli/util/YamlHelper.java +++ b/app/src/main/java/io/seqera/wave/cli/util/YamlHelper.java @@ -21,6 +21,9 @@ import io.seqera.wave.api.ContainerInspectResponse; import io.seqera.wave.api.SubmitContainerTokenResponse; +import io.seqera.wave.cli.model.ContainerInspectResponseEx; +import io.seqera.wave.cli.model.ContainerSpecEx; +import io.seqera.wave.cli.model.LayerRef; import io.seqera.wave.core.spec.ConfigSpec; import io.seqera.wave.core.spec.ContainerSpec; import io.seqera.wave.core.spec.ManifestSpec; @@ -51,7 +54,7 @@ public static String toYaml(SubmitContainerTokenResponse resp) { return yaml.dump(resp); } - public static String toYaml(ContainerInspectResponse resp) { + public static String toYaml(ContainerInspectResponseEx resp) { final DumperOptions opts = new DumperOptions(); opts.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); opts.setAllowReadOnlyProperties(true); @@ -59,9 +62,12 @@ public static String toYaml(ContainerInspectResponse resp) { final Representer representer = new Representer(opts) { { addClassTag(ContainerSpec.class, Tag.MAP); + addClassTag(ContainerSpecEx.class, Tag.MAP); addClassTag(ConfigSpec.class, Tag.MAP); addClassTag(ManifestSpec.class, Tag.MAP); addClassTag(ContainerInspectResponse.class, Tag.MAP); + addClassTag(ContainerInspectResponseEx.class, Tag.MAP); + addClassTag(LayerRef.class, Tag.MAP); representers.put(Instant.class, data -> representScalar(Tag.STR, data.toString())); } }; diff --git a/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy b/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy index 0e73583..3ab2469 100644 --- a/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy +++ b/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy @@ -20,11 +20,11 @@ package io.seqera.wave.cli import java.nio.file.Files import java.time.Instant -import io.seqera.wave.api.ContainerInspectResponse import io.seqera.wave.api.SubmitContainerTokenResponse +import io.seqera.wave.cli.exception.IllegalCliArgumentException +import io.seqera.wave.cli.model.ContainerInspectResponseEx import io.seqera.wave.core.spec.ContainerSpec import io.seqera.wave.util.TarUtils -import io.seqera.wave.cli.exception.IllegalCliArgumentException import picocli.CommandLine import spock.lang.Specification import spock.lang.Unroll @@ -103,13 +103,13 @@ class AppTest extends Specification { def app = new App() String[] args = ["--output", "json"] and: - def resp = new ContainerInspectResponse( new ContainerSpec('docker.io', 'busybox', 'latest', 'sha:12345', null, null, null) ) + def resp = new ContainerInspectResponseEx( new ContainerSpec('docker.io', 'https://docker.io', 'busybox', 'latest', 'sha:12345', null, null) ) when: new CommandLine(app).parseArgs(args) def result = app.dumpOutput(resp) then: - result == '{"container":{"digest":"sha:12345","imageName":"busybox","reference":"latest","registry":"docker.io"}}' + result == '{"container":{"digest":"sha:12345","hostName":"https://docker.io","imageName":"busybox","reference":"latest","registry":"docker.io"}}' } def 'should dump inspect to yaml' () { @@ -117,7 +117,7 @@ class AppTest extends Specification { def app = new App() String[] args = ["--output", "yaml"] and: - def resp = new ContainerInspectResponse( new ContainerSpec('docker.io', 'busybox', 'latest', 'sha:12345', null, null, null) ) + def resp = new ContainerInspectResponseEx( new ContainerSpec('docker.io', 'https://docker.io', 'busybox', 'latest', 'sha:12345', null, null) ) when: new CommandLine(app).parseArgs(args) @@ -127,6 +127,7 @@ class AppTest extends Specification { container: config: null digest: sha:12345 + hostName: https://docker.io imageName: busybox manifest: null reference: latest diff --git a/app/src/test/groovy/io/seqera/wave/cli/json/JsonHelperTest.groovy b/app/src/test/groovy/io/seqera/wave/cli/json/JsonHelperTest.groovy index 5fed98e..4785e74 100644 --- a/app/src/test/groovy/io/seqera/wave/cli/json/JsonHelperTest.groovy +++ b/app/src/test/groovy/io/seqera/wave/cli/json/JsonHelperTest.groovy @@ -18,8 +18,9 @@ package io.seqera.wave.cli.json import io.seqera.wave.api.SubmitContainerTokenRequest -import spock.lang.Specification; - +import io.seqera.wave.cli.model.ContainerInspectResponseEx +import io.seqera.wave.core.spec.ContainerSpec +import spock.lang.Specification /** * @author Paolo Di Tommaso */ @@ -43,5 +44,15 @@ class JsonHelperTest extends Specification { result.containerImage == 'quay.io/nextflow/bash:latest' } + def 'should convert response to json' () { + given: + def spec = new ContainerSpec('docker.io', 'https://docker.io', 'ubuntu', '22.04', 'sha:12345', null, null) + def resp = new ContainerInspectResponseEx(spec) + + when: + def result = JsonHelper.toJson(resp) + then: + result == '{"container":{"digest":"sha:12345","hostName":"https://docker.io","imageName":"ubuntu","reference":"22.04","registry":"docker.io"}}' + } } diff --git a/app/src/test/groovy/io/seqera/wave/cli/util/YamlHelperTest.groovy b/app/src/test/groovy/io/seqera/wave/cli/util/YamlHelperTest.groovy index 91c0568..94ea941 100644 --- a/app/src/test/groovy/io/seqera/wave/cli/util/YamlHelperTest.groovy +++ b/app/src/test/groovy/io/seqera/wave/cli/util/YamlHelperTest.groovy @@ -17,12 +17,13 @@ package io.seqera.wave.cli.util - import java.time.Instant -import io.seqera.wave.api.ContainerInspectResponse import io.seqera.wave.api.SubmitContainerTokenResponse +import io.seqera.wave.cli.model.ContainerInspectResponseEx import io.seqera.wave.core.spec.ContainerSpec +import io.seqera.wave.core.spec.ManifestSpec +import io.seqera.wave.core.spec.ObjectRef import spock.lang.Specification /** * @@ -58,8 +59,10 @@ class YamlHelperTest extends Specification { def 'should convert response to yaml' () { given: - def spec = new ContainerSpec('docker.io','ubuntu','22.04','sha:12345', null, null, null) - def resp = new ContainerInspectResponse(spec) + def layers = [ new ObjectRef('text', 'sha256:12345', 100, null), new ObjectRef('text', 'sha256:67890', 200, null) ] + def manifest = new ManifestSpec(2, 'some/media', null, layers, [one: '1', two:'2']) + def spec = new ContainerSpec('docker.io', 'https://docker.io', 'ubuntu','22.04','sha:12345', null, manifest) + def resp = new ContainerInspectResponseEx(spec) when: def result = YamlHelper.toYaml(resp) @@ -68,8 +71,26 @@ class YamlHelperTest extends Specification { container: config: null digest: sha:12345 + hostName: https://docker.io imageName: ubuntu - manifest: null + manifest: + annotations: + one: '1' + two: '2' + config: null + layers: + - annotations: null + digest: sha256:12345 + mediaType: text + size: 100 + uri: https://docker.io/v2/ubuntu/blobs/sha256:12345 + - annotations: null + digest: sha256:67890 + mediaType: text + size: 200 + uri: https://docker.io/v2/ubuntu/blobs/sha256:67890 + mediaType: some/media + schemaVersion: 2 reference: '22.04' registry: docker.io '''.stripIndent(true)