Description of the issue:
I encounter the same problem with gradle plugin. The same configs works for version 0.9.7, but gives error in 0.9.8. It looks like this issue but there are not useful solution there.
Expected behavior:
Should work with both versions
Steps to reproduce:
./gradlew clean jib with version 0.9.7 (success)./gradlew clean jib with version 0.9.8 (error)Environment:
MacOs version 10.13.6
gradle wrapper 4.8.1
jib-gradle-plugin Configuration:
jib {
from {
image = 'openjdk:8-jdk-alpine'
auth {
username = dockerUsername
password = dockerPassword
}
}
to {
image = "demonian/${bootJar.baseName}:${bootJar.version}"
auth {
username = dockerUsername
password = dockerPassword
}
}
container {
useCurrentTimestamp = true
}
}
Log output:
### Success with jib 0.9.7 logs:
Built and pushed image as demonian/prime:0.0.1-SNAPSHOT
:jib (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 9.24 secs.
BUILD SUCCESSFUL in 10s
4 actionable tasks: 4 executed
### Error with jib 0.9.8, but according to logs I see that it pushed the image:
Authenticating with push to registry.hub.docker.com : 1755.206 ms
RUNNING Pushing BLOB digest: sha256:196d20239e57e877897f1804f66edb53fa657eefd403191e40b2c451fc66a223, size: 142
RUNNING Pushing BLOB digest: sha256:5b8637e76e31c34f9f794885baae1511bc52127ba052dd0a8011df837122bc5c, size: 749
RUNNING Pushing BLOB digest: sha256:a19961ec08095eb70ca3a070945223dcf51500a255a745272276139bf0a29f4e, size: 15515814
BLOB : digest: sha256:5b8637e76e31c34f9f794885baae1511bc52127ba052dd0a8011df837122bc5c, size: 749 already exists on registry
Pushing BLOB digest: sha256:5b8637e76e31c34f9f794885baae1511bc52127ba052dd0a8011df837122bc5c, size: 749 : 752.68 ms
BLOB : digest: sha256:196d20239e57e877897f1804f66edb53fa657eefd403191e40b2c451fc66a223, size: 142 already exists on registry
Pushing BLOB digest: sha256:196d20239e57e877897f1804f66edb53fa657eefd403191e40b2c451fc66a223, size: 142 : 763.521 ms
BLOB : digest: sha256:a19961ec08095eb70ca3a070945223dcf51500a255a745272276139bf0a29f4e, size: 15515814 already exists on registry
Pushing BLOB digest: sha256:a19961ec08095eb70ca3a070945223dcf51500a255a745272276139bf0a29f4e, size: 15515814 : 876.491 ms
Pulling base image manifest : 4992.779 ms
RUNNING Setting up base image caching
RUNNING Pulling base image layer sha256:8e3ba11ec2a2b39ab372c60c16b421536e50e5ce64a0bc81765c2e38381bcff6
Pulling base image layer sha256:8e3ba11ec2a2b39ab372c60c16b421536e50e5ce64a0bc81765c2e38381bcff6 : 0.116 ms
RUNNING Pulling base image layer sha256:311ad0da45338842480bf25c6e6b7bb133b7b8cf709c3470db171ec370da5539
Setting up base image caching : 1.33 ms
Pulling base image layer sha256:311ad0da45338842480bf25c6e6b7bb133b7b8cf709c3470db171ec370da5539 : 0.214 ms
RUNNING Setting up to push layers
RUNNING Pulling base image layer sha256:df312c74ce16f20eeb87b5640db9b1579a53534bd3e9f3de1e916fc62744bcf4
Pulling base image layer sha256:df312c74ce16f20eeb87b5640db9b1579a53534bd3e9f3de1e916fc62744bcf4 : 0.025 ms
Setting up to push layers : 0.113 ms
RUNNING Pushing BLOB digest: sha256:311ad0da45338842480bf25c6e6b7bb133b7b8cf709c3470db171ec370da5539, size: 239
RUNNING Pushing BLOB digest: sha256:8e3ba11ec2a2b39ab372c60c16b421536e50e5ce64a0bc81765c2e38381bcff6, size: 2206542
RUNNING Pushing BLOB digest: sha256:df312c74ce16f20eeb87b5640db9b1579a53534bd3e9f3de1e916fc62744bcf4, size: 70581383
RUNNING Building container configuration
Finalizing...
Building container configuration : 0.89 ms
RUNNING Pushing container configuration
RUNNING Pushing BLOB digest: sha256:b2fe9730eabdbde8cf335e0d97f05095bbac43326cfd2405390ce367d5a0ef21, size: 966
Pushing container configuration : 43.376 ms
BLOB : digest: sha256:311ad0da45338842480bf25c6e6b7bb133b7b8cf709c3470db171ec370da5539, size: 239 already exists on registry
Pushing BLOB digest: sha256:311ad0da45338842480bf25c6e6b7bb133b7b8cf709c3470db171ec370da5539, size: 239 : 744.023 ms
BLOB : digest: sha256:8e3ba11ec2a2b39ab372c60c16b421536e50e5ce64a0bc81765c2e38381bcff6, size: 2206542 already exists on registry
Pushing BLOB digest: sha256:8e3ba11ec2a2b39ab372c60c16b421536e50e5ce64a0bc81765c2e38381bcff6, size: 2206542 : 757.223 ms
BLOB : digest: sha256:df312c74ce16f20eeb87b5640db9b1579a53534bd3e9f3de1e916fc62744bcf4, size: 70581383 already exists on registry
Pushing BLOB digest: sha256:df312c74ce16f20eeb87b5640db9b1579a53534bd3e9f3de1e916fc62744bcf4, size: 70581383 : 1066.132 ms
Pushing BLOB digest: sha256:b2fe9730eabdbde8cf335e0d97f05095bbac43326cfd2405390ce367d5a0ef21, size: 966 : 1453.371 ms
Building and pushing image : 6914.244 ms
> Task :jib FAILED
:jib (Thread[Task worker for ':',5,main]) completed. Took 7.046 secs.
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':jib'.
> Build image failed, perhaps you should make sure your credentials for 'registry.hub.docker.com' are set up correctly
* Try:
Run with --stacktrace option to get the stack trace. Run with --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 10s
### Stacktrace:
Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':jib'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:110)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter.execute(OutputDirectoryCreatingTaskExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:59)
at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54)
at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:59)
at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:101)
at org.gradle.api.internal.tasks.execution.FinalizeInputFilePropertiesTaskExecuter.execute(FinalizeInputFilePropertiesTaskExecuter.java:44)
at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:91)
at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:62)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:59)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.run(EventFiringTaskExecuter.java:51)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:317)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:309)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:185)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:97)
at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:46)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$ExecuteTaskAction.execute(DefaultTaskExecutionGraph.java:262)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$ExecuteTaskAction.execute(DefaultTaskExecutionGraph.java:246)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:136)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:130)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.execute(DefaultTaskPlanExecutor.java:201)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.executeWithTask(DefaultTaskPlanExecutor.java:192)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.run(DefaultTaskPlanExecutor.java:130)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
Caused by: org.gradle.api.GradleException: Build image failed, perhaps you should make sure your credentials for 'registry.hub.docker.com' are set up correctly
at com.google.cloud.tools.jib.gradle.BuildImageTask.buildImage(BuildImageTask.java:169)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:46)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:39)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:26)
at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:794)
at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:761)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:131)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:317)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:309)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:185)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:97)
at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:120)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:99)
... 30 more
Caused by: com.google.cloud.tools.jib.registry.RegistryUnauthorizedException: Unauthorized for registry.hub.docker.com/demonian/prime
at com.google.cloud.tools.jib.registry.RegistryEndpointCaller.call(RegistryEndpointCaller.java:252)
at com.google.cloud.tools.jib.registry.RegistryEndpointCaller.callWithAllowInsecureRegistryHandling(RegistryEndpointCaller.java:150)
at com.google.cloud.tools.jib.registry.RegistryEndpointCaller.call(RegistryEndpointCaller.java:140)
at com.google.cloud.tools.jib.registry.RegistryClient.callRegistryEndpoint(RegistryClient.java:356)
at com.google.cloud.tools.jib.registry.RegistryClient.pushBlob(RegistryClient.java:305)
at com.google.cloud.tools.jib.builder.steps.PushBlobStep.call(PushBlobStep.java:100)
at com.google.cloud.tools.jib.builder.steps.PushBlobStep.call(PushBlobStep.java:35)
at com.google.common.util.concurrent.CombinedFuture$CallableInterruptibleTask.runInterruptibly(CombinedFuture.java:181)
at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:57)
Caused by: com.google.api.client.http.HttpResponseException: 401 Unauthorized
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"demonian/prime","Action":"pull"},{"Type":"repository","Class":"","Name":"demonian/prime","Action":"push"},{"Type":"repository","Class":"","Name":"library/openjdk","Action":"pull"}]}]}
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1070)
at com.google.cloud.tools.jib.http.Connection.send(Connection.java:161)
at com.google.cloud.tools.jib.registry.RegistryEndpointCaller.call(RegistryEndpointCaller.java:213)
... 8 more
I can reproduce this issue, but let me check if what I am seeing is the same problem.
@Demonian try again with 0.9.8 after removing
from {
image = 'openjdk:8-jdk-alpine'
auth {
username = dockerUsername
password = dockerPassword
}
}
which will make Jib pull gcr.io/distroless/java and use it as a base image. In my case, it fails if pulling from Docker Hub but works if not.
Hmm, I think the bug I brought up earlier (seem bullet 2 in https://github.com/GoogleContainerTools/jib/pull/801) might have appeared earlier than I thought and appeared before we released 0.9.8. If this is true, we've already addressed it and there will be a fix in 0.9.9.
@chanseokoh can you see if you can reproduce this in 0.9.9-SNAPSHOT?
I was trying with Maven, but with 0.9.9-SNAPSHOT, I'm seeing this weird error when the <auth> section is defined:
[ERROR] Failed to execute goal com.google.cloud.tools:jib-maven-plugin:0.9.9-SNAPSHOT:build (default-cli) on project helloworld: Unable to parse configuration of mojo com.google.cloud.tools:jib-maven-plugin:0.9.9-SNAPSHOT:build for parameter auth: Cannot create instance of class com.google.cloud.tools.jib.maven.JibPluginConfiguration$AuthConfiguration: com.google.cloud.tools.jib.maven.JibPluginConfiguration$AuthConfiguration.<init>() -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginConfigurationException
This seems like a bug of its own.
@TadCordle tested 0.9.9-SNAPSHOT on Gradle, but it does not fix it.
@chanseokoh Yes, if i delete "from" fragment from jib then it start to work, but i think it is still the issue.
@Demonian yes, certainly this is a bug. Just wanted to confirm if what I reproduced is your problem.
So looks like this happens when you are pulling and pushing images to the same registry while pushing requires auth.
@chanseokoh I think that the main problem according to logs is that it try to pull the image from the same repo that is declared in "to" fragment even that there is another information in "from".
jib {
from {
image = 'library/openjdk:8-jdk-alpine'
}
to {
image = "demonian/prime:1"
auth {
username = dockerUsername
password = dockerPassword
}
}
container {
useCurrentTimestamp = true
}
}
In error we can see that it try to pull from "demonian/prime":
Unauthorized for registry.hub.docker.com/demonian/prime
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"demonian/prime","Action":"pull"},{"Type":"repository","Class":"","Name":"demonian/prime","Action":"push"},{"Type":"repository","Class":"","Name":"library/openjdk","Action":"pull"}]}]}
Am i right? Maybe this info could help to fix the issue =)
Ah, it looks like the cross-repository blob mount is giving the unauthorized error, since the token Jib authenticates for doing the push with the mount from the base repository is only authenticated for permissions to pull/push to the target image. For cross-repository blob mount to work, then token needs to have been authenticated for pull from the base image in addition to push to the target image. @briandealwis I think we can remove cross-repository blob mount if this is too much trouble to implement since it is only a minor optimization.
I think we can remove cross-repository blob mount if this is too much trouble to implement since it is only a minor optimization.
I understand this as not using mount at all, until we can implement proper auth. @coollog is that what you mean? With 0.9.7, we used to use mount (but without from).
For cross-repository blob mount to work, then token needs to have been authenticated for pull from the base image in addition to push to the target image.
Figured out how to do. In my example, currently the auth URL for pushing my image repository:francium25/image-built-with-jib-4 looks like this:
https://auth.docker.io/token?service=registry.docker.io&scope=repository:francium25/image-built-with-jib-4:pull,push
We just need to add another scope at the end for pulling the base image library/openjdk too:
https://auth.docker.io/token?service=registry.docker.io&scope=repository:francium25/image-built-with-jib-4:pull,push&scope=repository:library/openjdk:pull
So, we need to pass the base repository name (the image ref.) into RegistryAuthenticator for the AuthenticatePushStep (only when doing cross-repository blob mount)
and then have the right URL at
@chanseokoh
I understand this as not using
mountat all, until we can implement proper auth. @coollog is that what you mean? With 0.9.7, we used to usemount(but withoutfrom).
Yes, it might just not be worth it.
We just need to add another
scopeat the end for pulling the base imagelibrary/openjdk:pulltoo:
It might not be as simple as that, since if the base image repository requires authentication credentials as well, that approach may not work - we would need to somehow use both the base image and target image credentials in the authentication token challenge response.
Hmm... yeah, but now I see it could pull the base image from a different user account even if in the same registry hence requiring different username/password. Right, it might not be that simple.
Perhaps we could retry with a mount-less push if the mount-ed push fails.
I think trying to get it to work in the "base case", where there is no per-from and per-to auth specified, could provide a big win on Docker Hub.
Not tested, but providing both base and target Authorizations when calling the auth URL might just work? https://stackoverflow.com/a/38515091
But many others are saying it's not the standard and you can only ever send one Authorization. It may be wise to use mount only when the target credentials can be applied to the base image.
I think trying to get it to work in the "base case", where there is no per-from and per-to auth specified, could provide a big win on Docker Hub.
I think a simple check
will work wonders, and go for the mount only when one of the conditions are met. I believe the check can be done easily at AuthenticatePushStep.
Feel free to take this issue @chanseokoh — I've only started digging and you seem to have a direction.
I was just throwing out some ideas that I think might work without actually looking to the code hoping it will help you too. But if you are not sure about or don't get my ideas, I could also try this out briefly. Let me look into it.
Seems like this requires a bit of re-organizing AsyncSteps, possibly introducing a new one to determine ahead if we can do the cross-repository blob mount, because PushBlobStep requires Authorization from AuthenticatePushStep, but AuthenticatePushStep needs to know ahead if it should ask for the blob mount scope too.
In addition to https://github.com/GoogleContainerTools/jib/issues/808#issuecomment-411743591, it'd make sense to cover https://github.com/GoogleContainerTools/jib/pull/819#issuecomment-411921112.
It'd be nice to improve this by adding another heuristic that we also do the cross-repo mount if
- Jib found no credentials for the base image
- Jib could pull the base image (i.e., the image was public)
- the base and the target registries are same
But considering what @coollog said in https://github.com/GoogleContainerTools/jib/issues/808#issuecomment-411502671, now I'm tempted to just not do the mount at all. If this doesn't buy us much, it makes sense to keep things simple.
It would seem we'd need to pull out the authorization into a central service that would know base and target image information.
I think an easier workaround would be what I suggested above:
Perhaps we could retry with a mount-less push if the mount-ed push fails.
The cost of another round-trip would be balanced by possibly avoiding the upload of a multi-megabyte layer.
@briandealwis feel free to take over from here. I won’t continue on #819 at this point. Was probably going to close it actually.
Perhaps we could retry with a mount-less push if the mount-ed push fails.
I'm thinking this might cause more common cases to fail, since it is much more common that the base and target repositories require different authentication than the same authentication. This would make, for example, the more common cases of pulling from a public Docker Hub repository to a user's own Docker Hub repository always incur the extra mounted push cost.
But it's a win on other registries like GCR. Let me try to get some timings for comparison.
I hacked up PushBlobStep to wrap the RegistryClient#pushBlob() in a try-catch on a RegistryUnauthorizedException so as to retry the push without a mount. I then compared two builds to docker hub:
$ mvn -DjibVersion=0.9.9-SNAPSHOT -DfromImage=library/openjdk:10-jre -Dimage=briandealwis/jib099 jib:build
[INFO] Total time: 43.502 s
[INFO] Total time: 38.590 s
versus
$ mvn -DjibVersion=0.9.7 -DfromImage=library/openjdk:10-jre -Dimage=briandealwis/jib097 jib:build
[INFO] Total time: 35.068 s
[INFO] Total time: 32.124 s
It has a surprising impact! What's odd is that the timestamps on the HTTP requests (when logged) show the mount+from-ed POSTs take no real time.
Aha, I think we could just adjust AuthenticatePushStep to initiate RegistryAuthenticator with both the base and target repositories if on the same registry. This requires cascading support for multiple repositories throughout RegistryAuthenticator.
For now I'll just remove the support for mount/from.
Hi @Demonian , we have resolved this issue in the new release (version 0.9.9).