Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package werf for openSUSE:Factory checked in 
at 2026-11-13 18:34:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/werf (Old)
 and      /work/SRC/openSUSE:Factory/.werf.new.1251 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "werf"

Fri Nov 13 18:34:56 2026 rev:89 rq:1397245 version:2.84.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/werf/werf.changes        2026-10-29 
16:10:36.224789298 +0100
+++ /work/SRC/openSUSE:Factory/.werf.new.1251/werf.changes      2026-11-13 
18:38:55.515426790 +0100
@@ -1,0 +2,12 @@
+Fri Nov 13 05:32:47 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 2.84.0:
+  * Features
+    - build: publish cached images with require-built-images
+      (#8096) (bf5c561)
+- Update to version 2.83.1:
+  * Bug Fixes
+    - build: avoid panic on cache-repo errors without final-repo
+      (#8088) (acdca35)
+
+-------------------------------------------------------------------

Old:
----
  werf-2.83.0.obscpio

New:
----
  werf-2.84.0.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ werf.spec ++++++
--- /var/tmp/diff_new_pack.poTGC7/_old  2026-11-13 18:39:00.813650955 +0100
+++ /var/tmp/diff_new_pack.poTGC7/_new  2026-11-13 18:39:00.817651124 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           werf
-Version:        2.83.0
+Version:        2.84.0
 Release:        0
 Summary:        CLI for the Werf CI/CD system
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.poTGC7/_old  2026-11-13 18:39:00.851652562 +0100
+++ /var/tmp/diff_new_pack.poTGC7/_new  2026-11-13 18:39:00.855652732 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/werf/werf.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v2.83.0</param>
+    <param name="revision">refs/tags/v2.84.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.poTGC7/_old  2026-11-13 18:39:00.880653789 +0100
+++ /var/tmp/diff_new_pack.poTGC7/_new  2026-11-13 18:39:00.884653959 +0100
@@ -3,6 +3,6 @@
                 <param name="url">https://github.com/werf/werf</param>
               <param 
name="changesrevision">508fb9aee29baca228674ba7a3d2d4fa1f5844d5</param></service><service
 name="tar_scm">
                 <param name="url">https://github.com/werf/werf.git</param>
-              <param 
name="changesrevision">393e5d5e9fe4bea56b226174e3d4397bc78ce602</param></service></servicedata>
+              <param 
name="changesrevision">4ca847c4b39ff5174f9174b41613133f4dfdcd62</param></service></servicedata>
 (No newline at EOF)
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/werf/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.werf.new.1251/vendor.tar.gz differ: char 142, line 2

++++++ werf-2.83.0.obscpio -> werf-2.84.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/.release-please/2/manifest.json 
new/werf-2.84.0/.release-please/2/manifest.json
--- old/werf-2.83.0/.release-please/2/manifest.json     2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/.release-please/2/manifest.json     2026-10-20 
17:16:12.000000000 +0200
@@ -1,3 +1,3 @@
 {
-  ".": "2.83.0"
+  ".": "2.84.0"
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/CHANGELOG.md new/werf-2.84.0/CHANGELOG.md
--- old/werf-2.83.0/CHANGELOG.md        2026-10-15 12:45:06.000000000 +0200
+++ new/werf-2.84.0/CHANGELOG.md        2026-10-20 17:16:12.000000000 +0200
@@ -1,5 +1,19 @@
 # Changelog
 
+## [2.84.0](https://github.com/werf/werf/compare/v2.83.1...v2.84.0) 
(2026-10-20)
+
+
+### Features
+
+* **build:** publish cached images with require-built-images 
([#8096](https://github.com/werf/werf/issues/8096)) 
([bf5c561](https://github.com/werf/werf/commit/bf5c56112b1759b886690199ef161e876be94dc5))
+
+## [2.83.1](https://github.com/werf/werf/compare/v2.83.0...v2.83.1) 
(2026-10-19)
+
+
+### Bug Fixes
+
+* **build:** avoid panic on cache-repo errors without final-repo 
([#8088](https://github.com/werf/werf/issues/8088)) 
([acdca35](https://github.com/werf/werf/commit/acdca350932073444c4d385c3325de6bfc390b59))
+
 ## [2.83.0](https://github.com/werf/werf/compare/v2.82.0...v2.83.0) 
(2026-10-15)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/cmd/werf/build/main.go 
new/werf-2.84.0/cmd/werf/build/main.go
--- old/werf-2.83.0/cmd/werf/build/main.go      2026-10-15 12:45:06.000000000 
+0200
+++ new/werf-2.84.0/cmd/werf/build/main.go      2026-10-20 17:16:12.000000000 
+0200
@@ -44,6 +44,9 @@
                },
                RunE: func(cmd *cobra.Command, args []string) error {
                        ctx := cmd.Context()
+                       if common.GetRequireBuiltImages(&commonCmdData) && 
common.GetCheckBuiltImages(&commonCmdData) {
+                               return fmt.Errorf("--require-built-images 
cannot be used with --check-built-images")
+                       }
 
                        defer global_warnings.PrintGlobalWarnings(ctx)
 
@@ -104,6 +107,8 @@
 
        common.SetupParallelOptions(&commonCmdData, cmd, 
common.DefaultBuildParallelTasksLimit)
        common.SetupCheckBuiltImages(&commonCmdData, cmd)
+       common.SetupRequireBuiltImages(&commonCmdData, cmd)
+       cmd.Flags().Lookup("require-built-images").Usage = "Publish previously 
built images and custom tags without running build instructions. Reuse stages 
from primary and secondary repos; fail if a required stage is missing. 
Incompatible with --check-built-images (default $WERF_REQUIRE_BUILT_IMAGES)"
        common.SetupFollow(&commonCmdData, cmd)
 
        common.SetupDisableAutoHostCleanup(&commonCmdData, cmd)
@@ -217,6 +222,8 @@
                return err
        }
 
+       buildOptions.RequireBuiltImages = 
common.GetRequireBuiltImages(&commonCmdData)
+
        conveyorOptions, err := common.GetConveyorOptionsWithParallel(ctx, 
&commonCmdData, imagesToProcess, buildOptions)
        if err != nil {
                return err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/werf-2.83.0/cmd/werf/build/require_built_images_test.go 
new/werf-2.84.0/cmd/werf/build/require_built_images_test.go
--- old/werf-2.83.0/cmd/werf/build/require_built_images_test.go 1970-01-01 
01:00:00.000000000 +0100
+++ new/werf-2.84.0/cmd/werf/build/require_built_images_test.go 2026-10-20 
17:16:12.000000000 +0200
@@ -0,0 +1,65 @@
+package build
+
+import (
+       "io"
+       "os"
+       "strings"
+       "testing"
+
+       "github.com/onsi/ginkgo/v2"
+       "github.com/onsi/gomega"
+
+       "github.com/werf/werf/v2/cmd/werf/common"
+)
+
+func TestBuildCommand(t *testing.T) {
+       gomega.RegisterFailHandler(ginkgo.Fail)
+       ginkgo.RunSpecs(t, "Build Command Suite")
+}
+
+var _ = ginkgo.Describe("Require built images flags", func() {
+       ginkgo.BeforeEach(func() {
+               for _, entry := range os.Environ() {
+                       key, _, _ := strings.Cut(entry, "=")
+                       if strings.HasPrefix(key, "WERF_") {
+                               ginkgo.GinkgoT().Setenv(key, "")
+                       }
+               }
+       })
+
+       ginkgo.DescribeTable("rejects invalid mode combinations before 
initializing the build",
+               func(ctx ginkgo.SpecContext, args []string, env 
map[string]string, expected string) {
+                       for key, value := range env {
+                               ginkgo.GinkgoT().Setenv(key, value)
+                       }
+                       cmd := NewCmd(ctx)
+                       cmd.SetOut(io.Discard)
+                       cmd.SetErr(io.Discard)
+                       cmd.SetArgs(append([]string{}, args...))
+                       
gomega.Expect(cmd.ExecuteContext(ctx)).To(gomega.MatchError(gomega.ContainSubstring(expected)))
+               },
+               ginkgo.Entry("check flag", []string{"--require-built-images", 
"--check-built-images"}, nil, "cannot be used with --check-built-images"),
+               ginkgo.Entry("short flag", []string{"-Z", 
"--check-built-images"}, nil, "cannot be used with --check-built-images"),
+               ginkgo.Entry("check environment", 
[]string{"--require-built-images"}, 
map[string]string{"WERF_CHECK_BUILT_IMAGES": "true"}, "cannot be used with 
--check-built-images"),
+               ginkgo.Entry("require environment", 
[]string{"--check-built-images"}, 
map[string]string{"WERF_REQUIRE_BUILT_IMAGES": "true"}, "cannot be used with 
--check-built-images"),
+               ginkgo.Entry("both environments", nil, 
map[string]string{"WERF_REQUIRE_BUILT_IMAGES": "true", 
"WERF_CHECK_BUILT_IMAGES": "true"}, "cannot be used with --check-built-images"),
+       )
+       ginkgo.DescribeTable("selects independent check and require modes", 
func(ctx ginkgo.SpecContext, args []string, env map[string]string, require, 
check bool) {
+               for key, value := range env {
+                       ginkgo.GinkgoT().Setenv(key, value)
+               }
+               cmd := NewCmd(ctx)
+               gomega.Expect(cmd.ParseFlags(args)).To(gomega.Succeed())
+               
gomega.Expect(common.GetRequireBuiltImages(&commonCmdData)).To(gomega.Equal(require))
+               
gomega.Expect(common.GetCheckBuiltImages(&commonCmdData)).To(gomega.Equal(check))
+       },
+               ginkgo.Entry("ordinary build", nil, nil, false, false),
+               ginkgo.Entry("require flag", 
[]string{"--require-built-images"}, nil, true, false),
+               ginkgo.Entry("short flag", []string{"-Z"}, nil, true, false),
+               ginkgo.Entry("require environment", nil, 
map[string]string{"WERF_REQUIRE_BUILT_IMAGES": "true"}, true, false),
+               ginkgo.Entry("check flag", []string{"--check-built-images"}, 
nil, false, true),
+               ginkgo.Entry("check environment", nil, 
map[string]string{"WERF_CHECK_BUILT_IMAGES": "true"}, false, true),
+               ginkgo.Entry("explicit require override", 
[]string{"--require-built-images=false"}, 
map[string]string{"WERF_REQUIRE_BUILT_IMAGES": "true"}, false, false),
+               ginkgo.Entry("explicit check override", 
[]string{"--check-built-images=false", "-Z"}, 
map[string]string{"WERF_CHECK_BUILT_IMAGES": "true"}, true, false),
+       )
+})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/cmd/werf/common/cmd_data.go 
new/werf-2.84.0/cmd/werf/common/cmd_data.go
--- old/werf-2.83.0/cmd/werf/common/cmd_data.go 2026-10-15 12:45:06.000000000 
+0200
+++ new/werf-2.84.0/cmd/werf/common/cmd_data.go 2026-10-20 17:16:12.000000000 
+0200
@@ -47,10 +47,9 @@
        SecondaryStagesStorage *[]string
        CacheStagesStorage     *[]string
 
-       CheckBuiltImages       *bool
-       LegacyCheckBuiltImages *bool // TODO(major): remove
-       RequireBuiltImages     *bool
-       StubTags               *bool
+       CheckBuiltImages   *bool
+       RequireBuiltImages *bool
+       StubTags           *bool
 
        AddCustomTag *[]string
        UseCustomTag *string
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/cmd/werf/common/common.go 
new/werf-2.84.0/cmd/werf/common/common.go
--- old/werf-2.83.0/cmd/werf/common/common.go   2026-10-15 12:45:06.000000000 
+0200
+++ new/werf-2.84.0/cmd/werf/common/common.go   2026-10-20 17:16:12.000000000 
+0200
@@ -853,12 +853,6 @@
 func SetupCheckBuiltImages(cmdData *CmdData, cmd *cobra.Command) {
        cmdData.CheckBuiltImages = new(bool)
        cmd.Flags().BoolVarP(cmdData.CheckBuiltImages, "check-built-images", 
"", util.GetBoolEnvironmentDefaultFalse("WERF_CHECK_BUILT_IMAGES"), "Check that 
all used images are previously built and exist in repo. Exits with error if 
needed images are not cached and so require to run build instructions (default 
$WERF_CHECK_BUILT_IMAGES)")
-
-       cmdData.LegacyCheckBuiltImages = new(bool)
-       cmd.Flags().BoolVarP(cmdData.LegacyCheckBuiltImages, 
"require-built-images", "Z", false, "Check that all used images are previously 
built and exist in repo. Exits with error if needed images are not cached and 
so require to run build instructions")
-       if err := cmd.Flags().MarkHidden("require-built-images"); err != nil {
-               panic(err)
-       }
 }
 
 func SetupStubTags(cmdData *CmdData, cmd *cobra.Command) {
@@ -1453,7 +1447,7 @@
 }
 
 func GetCheckBuiltImages(cmdData *CmdData) bool {
-       return option.PtrValueOrDefault(cmdData.CheckBuiltImages, false) || 
option.PtrValueOrDefault(cmdData.LegacyCheckBuiltImages, false)
+       return option.PtrValueOrDefault(cmdData.CheckBuiltImages, false)
 }
 
 func isImagesReadOnly(cmdData *CmdData) bool {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/cmd/werf/common/host_cleanup_test.go 
new/werf-2.84.0/cmd/werf/common/host_cleanup_test.go
--- old/werf-2.83.0/cmd/werf/common/host_cleanup_test.go        2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/cmd/werf/common/host_cleanup_test.go        2026-10-20 
17:16:12.000000000 +0200
@@ -32,8 +32,6 @@
                gomega.Expect(skipAutoHostCleanup(cmdData)).To(gomega.BeTrue())
        },
                ginkgo.Entry("check flag", "--check-built-images"),
-               ginkgo.Entry("legacy flag", "--require-built-images"),
-               ginkgo.Entry("short flag", "-Z"),
                ginkgo.Entry("environment", ""),
        )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/cmd/werf/common/storage_readonly_test.go 
new/werf-2.84.0/cmd/werf/common/storage_readonly_test.go
--- old/werf-2.83.0/cmd/werf/common/storage_readonly_test.go    2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/cmd/werf/common/storage_readonly_test.go    2026-10-20 
17:16:12.000000000 +0200
@@ -36,8 +36,6 @@
                gomega.Expect(recorder.recordedWrites()).To(gomega.BeEmpty())
        },
                ginkgo.Entry("check flag", "--check-built-images"),
-               ginkgo.Entry("legacy flag", "--require-built-images"),
-               ginkgo.Entry("short flag", "-Z"),
                ginkgo.Entry("environment", ""),
        )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/werf-2.83.0/docs/_includes/reference/cli/werf_build.md 
new/werf-2.84.0/docs/_includes/reference/cli/werf_build.md
--- old/werf-2.83.0/docs/_includes/reference/cli/werf_build.md  2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/docs/_includes/reference/cli/werf_build.md  2026-10-20 
17:16:12.000000000 +0200
@@ -262,6 +262,10 @@
             repo Harbor username (default $WERF_REPO_HARBOR_USERNAME)
       --repo-quay-token=""
             repo quay.io token (default $WERF_REPO_QUAY_TOKEN)
+  -Z, --require-built-images=false
+            Publish previously built images and custom tags without running 
build instructions.     
+            Reuse stages from primary and secondary repos; fail if a required 
stage is missing.     
+            Incompatible with --check-built-images (default 
$WERF_REQUIRE_BUILT_IMAGES)
       --save-build-report=false
             Save build report (by default $WERF_SAVE_BUILD_REPORT or false). 
Its path and format    
             configured with --build-report-path
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/docs/pages_en/usage/build/process.md 
new/werf-2.84.0/docs/pages_en/usage/build/process.md
--- old/werf-2.83.0/docs/pages_en/usage/build/process.md        2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/docs/pages_en/usage/build/process.md        2026-10-20 
17:16:12.000000000 +0200
@@ -169,12 +169,12 @@
 
 ### Checking that images are built
 
-`werf build --check-built-images` (aliases: `--require-built-images`, `-Z`, 
`$WERF_CHECK_BUILT_IMAGES`), and `--require-built-images` on the commands that 
process images without building them, check that every image the project needs 
is already published. Missing stages produce `stages required`; unavailable 
output images, custom tags or legacy import metadata also cause the check to 
fail.
+`werf build --check-built-images` (or `$WERF_CHECK_BUILT_IMAGES`), and 
`--require-built-images` on the commands that process images without building 
them, check that every image the project needs is already published. Missing 
stages produce `stages required`; unavailable output images, custom tags or 
legacy import metadata also cause the check to fail.
 
 The check is read-only, and stage discovery is limited to the main repository:
 
 - a stage found in a `--secondary-repo` is **not** promoted into the main 
repository, and secondary stage lists are not requested — so a project whose 
stages only exist in a secondary repository fails the check until a regular 
build copies them over;
-- nothing is published: no stage, no manifest list for a multi-platform image, 
no custom tag, no managed-image record and no Git metadata. Custom tags are 
verified to exist instead of being created. `werf build --check-built-images` 
and its aliases also skip synchronization registration and use host-local 
locks; other commands using `--require-built-images` retain their normal 
synchronization initialization. The automatic host cleanup is not run either, 
so a check never deletes local cache data or local backend images.
+- nothing is published: no stage, no manifest list for a multi-platform image, 
no custom tag, no managed-image record and no Git metadata. Custom tags are 
verified to exist instead of being created. `werf build --check-built-images` 
also skips synchronization registration and uses host-local locks; other 
commands using `--require-built-images` retain their normal synchronization 
initialization. The automatic host cleanup is not run either, so a check never 
deletes local cache data or local backend images.
 - existing legacy import metadata may be read from primary or secondary 
storage without copying it. If import resolution yields missing, broken or 
empty metadata, the check fails instead of generating a checksum; run a regular 
build to create or repair that metadata;
 - the configured output is still validated, read-only: with a `--final-repo`, 
the final image is required to exist there and is not copied into it, so the 
check never reports an image as available at an address that does not have it.
 
@@ -182,6 +182,18 @@
 
 `werf stages copy` also transfers existing legacy import metadata whose 
checksums match the copied stages, including registry/archive round-trips and 
copies selected by a build report. Old archives without that metadata still 
require a regular build after restoring them before read-only checks can 
succeed.
 
+### Publishing previously built images
+
+`werf build --require-built-images` (or `-Z`, 
`WERF_REQUIRE_BUILT_IMAGES=true`) reuses primary and secondary stages and 
publishes images, custom tags and metadata without running build instructions. 
If a required stage is missing, the command fails. The optional `--final-repo` 
selects a separate destination; without it, publication uses the primary 
repository. Normal synchronization and automatic host cleanup remain enabled. 
Check and require modes cannot be enabled together.
+
+Legacy Stapel imports may still require a container backend: when import 
metadata is missing, broken or empty, require mode runs a checksum container 
and publishes the regenerated metadata. This does not execute stage build 
instructions.
+
+```shell
+werf build --require-built-images --repo registry.example.org/stage 
--final-repo registry.example.org/prod
+```
+
+`build --require-built-images` and `-Z` no longer act as aliases for 
`--check-built-images`: they may write to registries and need the corresponding 
permissions. Replace them with `--check-built-images` in read-only validation 
jobs. `WERF_REQUIRE_BUILT_IMAGES`, previously ignored by `build`, now prohibits 
running build instructions; unset it or use `--require-built-images=false` for 
ordinary builds.
+
 ## Parallelism and image assembly order
 
 <!-- reference: 
https://werf.io/docs/v2/internals/build_process.html#parallel-build -->
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/docs/pages_ru/usage/build/process.md 
new/werf-2.84.0/docs/pages_ru/usage/build/process.md
--- old/werf-2.83.0/docs/pages_ru/usage/build/process.md        2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/docs/pages_ru/usage/build/process.md        2026-10-20 
17:16:12.000000000 +0200
@@ -168,12 +168,12 @@
 
 ### Проверка того, что образы собраны
 
-`werf build --check-built-images` (алиасы: `--require-built-images`, `-Z`, 
`$WERF_CHECK_BUILT_IMAGES`), а также `--require-built-images` у команд, которые 
работают с образами, но не собирают их, проверяют, что все необходимые проекту 
образы уже опубликованы. Отсутствие стадий приводит к ошибке `stages required`; 
недоступность выходных образов, произвольных тегов или метаданных legacy import 
также завершает проверку ошибкой.
+`werf build --check-built-images` (или `$WERF_CHECK_BUILT_IMAGES`), а также 
`--require-built-images` у команд, которые работают с образами, но не собирают 
их, проверяют, что все необходимые проекту образы уже опубликованы. Отсутствие 
стадий приводит к ошибке `stages required`; недоступность выходных образов, 
произвольных тегов или метаданных legacy import также завершает проверку 
ошибкой.
 
 Проверка выполняется только на чтение, а поиск стадий ограничен основным 
репозиторием:
 
 - стадия, найденная в `--secondary-repo`, **не** копируется в основной 
репозиторий, а списки стадий вторичных репозиториев не запрашиваются — поэтому 
проект, стадии которого есть только во вторичном репозитории, не пройдёт 
проверку, пока обычная сборка не скопирует их;
-- ничего не публикуется: ни стадии, ни manifest list для мультиплатформенного 
образа, ни произвольные теги, ни записи managed-образов, ни Git-метаданные. 
Произвольные теги вместо создания проверяются на существование. `werf build 
--check-built-images` и его алиасы также пропускают регистрацию синхронизации и 
используют локальные блокировки; остальные команды с `--require-built-images` 
сохраняют обычную инициализацию синхронизации. Автоматическая очистка хоста 
тоже не запускается, поэтому проверка никогда не удаляет данные локального кеша 
или локальные образы бэкенда.
+- ничего не публикуется: ни стадии, ни manifest list для мультиплатформенного 
образа, ни произвольные теги, ни записи managed-образов, ни Git-метаданные. 
Произвольные теги вместо создания проверяются на существование. `werf build 
--check-built-images` также пропускает регистрацию синхронизации и использует 
локальные блокировки; остальные команды с `--require-built-images` сохраняют 
обычную инициализацию синхронизации. Автоматическая очистка хоста тоже не 
запускается, поэтому проверка никогда не удаляет данные локального кеша или 
локальные образы бэкенда.
 - существующие метаданные legacy import могут читаться из основного или 
вторичного хранилища без копирования. Если при разрешении импорта метаданные 
отсутствуют, повреждены или содержат пустую контрольную сумму, проверка 
завершается ошибкой вместо генерации checksum; выполните обычную сборку, чтобы 
создать или восстановить метаданные;
 - настроенный результат сборки по-прежнему проверяется, только на чтение: при 
`--final-repo` финальный образ обязан существовать там и не копируется туда, 
поэтому проверка никогда не сообщает об образе как о доступном по адресу, где 
его нет.
 
@@ -181,6 +181,18 @@
 
 `werf stages copy` также переносит существующие метаданные legacy import, 
контрольные суммы которых совпадают с импортами скопированных стадий, включая 
перенос через архив и выбор стадий по отчёту сборки. После восстановления 
старых архивов без этих метаданных по-прежнему требуется обычная сборка, прежде 
чем проверка только на чтение сможет завершиться успешно.
 
+### Публикация готовых образов
+
+`werf build --require-built-images` (или `-Z`, 
`WERF_REQUIRE_BUILT_IMAGES=true`) использует готовые стадии из основного и 
вторичных репозиториев и публикует образы, произвольные теги и метаданные без 
выполнения инструкций сборки. Если нужной стадии нет, команда завершается с 
ошибкой. Необязательный `--final-repo` задаёт отдельное назначение; без него 
публикация выполняется в основной репозиторий. Обычная синхронизация и 
автоматическая очистка хоста остаются включены. Режимы check и require нельзя 
включить одновременно.
+
+Для legacy Stapel import может потребоваться контейнерный бэкенд: если 
метаданные импорта отсутствуют, повреждены или содержат пустую контрольную 
сумму, require запускает контейнер расчёта checksum и публикует восстановленные 
метаданные. Инструкции сборки стадий при этом не выполняются.
+
+```shell
+werf build --require-built-images --repo registry.example.org/stage 
--final-repo registry.example.org/prod
+```
+
+`build --require-built-images` и `-Z` больше не являются алиасами 
`--check-built-images`: они могут записывать данные в registry и требуют 
соответствующих прав. Для проверок без записи замените их на 
`--check-built-images`. Переменная `WERF_REQUIRE_BUILT_IMAGES`, которую `build` 
раньше игнорировал, теперь запрещает выполнение инструкций сборки; для обычной 
сборки уберите её или укажите `--require-built-images=false`.
+
 ## Параллельность и порядок сборки образов
 
 <!-- прим. для перевода: на основе 
https://werf.io/docs/v2/internals/build_process.html#parallel-build -->
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/pkg/build/build_phase.go 
new/werf-2.84.0/pkg/build/build_phase.go
--- old/werf-2.83.0/pkg/build/build_phase.go    2026-10-15 12:45:06.000000000 
+0200
+++ new/werf-2.84.0/pkg/build/build_phase.go    2026-10-20 17:16:12.000000000 
+0200
@@ -41,7 +41,8 @@
 }
 
 type BuildOptions struct {
-       ImageBuildOptions container_backend.BuildOptions
+       RequireBuiltImages bool
+       ImageBuildOptions  container_backend.BuildOptions
        IntrospectOptions
 
        ReportPath       string
@@ -723,6 +724,9 @@
        }
 
        if !foundSuitableSecondaryStage {
+               if phase.RequireBuiltImages {
+                       return fmt.Errorf("image %q: required stage %s with 
digest %s not found; build images first without --require-built-images or 
WERF_REQUIRE_BUILT_IMAGES", img.GetName(), stg.Name(), stg.GetDigest())
+               }
                if phase.ShouldBeBuiltMode {
                        phase.printShouldBeBuiltError(ctx, img, stg)
                        return fmt.Errorf("stages required")
@@ -890,7 +894,11 @@
 
 ScanSecondaryStagesStorageList:
        for _, secondaryStagesStorage := range 
storageManager.GetSecondaryStagesStorageList() {
-               secondaryStages, err := 
storageManager.GetStageDescSetByDigestFromStagesStorageCached(ctx, 
stg.LogDetailedName(), stg.GetDigest(), phase.getPrevNonEmptyStageCreationTs(), 
secondaryStagesStorage)
+               lookup := 
storageManager.GetStageDescSetByDigestFromStagesStorageCached
+               if phase.RequireBuiltImages {
+                       lookup = 
storageManager.GetStageDescSetByDigestFromStagesStorageWithCache
+               }
+               secondaryStages, err := lookup(ctx, stg.LogDetailedName(), 
stg.GetDigest(), phase.getPrevNonEmptyStageCreationTs(), secondaryStagesStorage)
                if err != nil {
                        return false, err
                } else {
@@ -973,7 +981,7 @@
 
        storageManager := phase.Conveyor.StorageManager
        var stageDescSet imagePkg.StageDescSet
-       if phase.ShouldBeBuiltMode {
+       if phase.ShouldBeBuiltMode || phase.RequireBuiltImages {
                stageDescSet, err = 
storageManager.GetStageDescSetByDigestWithCache(ctx, stg.LogDetailedName(), 
stageDigest, phase.getPrevNonEmptyStageCreationTs())
        } else {
                stageDescSet, err = 
storageManager.GetStageDescSetByDigestFromStagesStorageCached(ctx, 
stg.LogDetailedName(), stageDigest, phase.getPrevNonEmptyStageCreationTs(), 
storageManager.GetStagesStorage())
@@ -1468,7 +1476,7 @@
 - manually with werf purge or werf host purge commands`)
                        logboek.Context(ctx).Warn().LogLn()
                        primaryStagesStorageAddress := 
phase.Conveyor.StorageManager.GetStagesStorage().String()
-                       logboek.Context(ctx).Warn().LogLn(reasonNumberFunc() + 
fmt.Sprintf(`You are checking that images are already built 
(--check-built-images / --require-built-images / -Z flag, 
WERF_CHECK_BUILT_IMAGES / WERF_REQUIRE_BUILT_IMAGES env), so only the primary 
stages storage %[1]s has been searched:
+                       logboek.Context(ctx).Warn().LogLn(reasonNumberFunc() + 
fmt.Sprintf(`You are checking that images are already built (e.g. werf build 
--check-built-images or WERF_CHECK_BUILT_IMAGES), so only the primary stages 
storage %[1]s has been searched:
 - If you expect images to be built and available in the registry, check the 
reasons above
 - Secondary stages storages (--secondary-repo and the implicit local one) are 
not consulted by the check and no stage is promoted into %[1]s
 - If the stage exists in a secondary stages storage only, publish it with an 
ordinary build first: werf build --repo %[1]s
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/pkg/build/check_mode_writes_test.go 
new/werf-2.84.0/pkg/build/check_mode_writes_test.go
--- old/werf-2.83.0/pkg/build/check_mode_writes_test.go 2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/pkg/build/check_mode_writes_test.go 2026-10-20 
17:16:12.000000000 +0200
@@ -121,15 +121,26 @@
                ginkgo.Entry("multiplatform", true, 
[]string{"PostMultiplatformImage", "AddStageCustomTag"}),
        )
 
-       ginkgo.DescribeTable("publishes outside check mode", func(ctx 
ginkgo.SpecContext, publish func(phase *BuildPhase) error, expectedWrites 
[]string) {
+       ginkgo.DescribeTable("publishes outside check mode", func(ctx 
ginkgo.SpecContext, require bool, publish func(phase *BuildPhase) error, 
expectedWrites []string) {
                phase, stagesStorage := newPhase(false)
+               phase.RequireBuiltImages = require
                gomega.Expect(publish(phase)).To(gomega.Succeed())
                
gomega.Expect(stagesStorage.writes).To(gomega.ConsistOf(expectedWrites))
        },
-               ginkgo.Entry("a single-platform image", func(phase *BuildPhase) 
error {
+               ginkgo.Entry("a single-platform image", false, func(phase 
*BuildPhase) error {
                        return phase.publishImageMetadata(context.Background(), 
"image0", singlePlatformImage())
                }, []string{"AddManagedImage", "PutImageMetadata", 
"AddStageCustomTag", "RegisterStageCustomTag"}),
-               ginkgo.Entry("a multiplatform image", func(phase *BuildPhase) 
error {
+               ginkgo.Entry("a multiplatform image", false, func(phase 
*BuildPhase) error {
+                       img := multiplatformImage()
+                       if err := 
phase.publishMultiplatformImageMetadata(context.Background(), "image0", img); 
err != nil {
+                               return err
+                       }
+                       return 
phase.publishMultiplatformImageCustomTags(context.Background(), "image0", img)
+               }, []string{"AddManagedImage", "PostMultiplatformImage", 
"PutImageMetadata", "AddStageCustomTag", "RegisterStageCustomTag"}),
+               ginkgo.Entry("a required single-platform image", true, 
func(phase *BuildPhase) error {
+                       return phase.publishImageMetadata(context.Background(), 
"image0", singlePlatformImage())
+               }, []string{"AddManagedImage", "PutImageMetadata", 
"AddStageCustomTag", "RegisterStageCustomTag"}),
+               ginkgo.Entry("a required multiplatform image", true, func(phase 
*BuildPhase) error {
                        img := multiplatformImage()
                        if err := 
phase.publishMultiplatformImageMetadata(context.Background(), "image0", img); 
err != nil {
                                return err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/pkg/build/helpers_test.go 
new/werf-2.84.0/pkg/build/helpers_test.go
--- old/werf-2.83.0/pkg/build/helpers_test.go   2026-10-15 12:45:06.000000000 
+0200
+++ new/werf-2.84.0/pkg/build/helpers_test.go   2026-10-20 17:16:12.000000000 
+0200
@@ -423,3 +423,53 @@
 func (m *publicationStorageManager) 
GetStageDescSetByDigestFromStagesStorageCached(context.Context, string, string, 
int64, storage.StagesStorage) (imagePkg.StageDescSet, error) {
        return imagePkg.NewStageDescSet(), nil
 }
+
+type requireLookupStorageManager struct{ *publicationStorageManager }
+
+var _ manager.StorageManagerInterface = (*requireLookupStorageManager)(nil)
+
+func (m *requireLookupStorageManager) GetStageDescSetByDigestWithCache(ctx 
context.Context, name, digest string, parentTs int64) (imagePkg.StageDescSet, 
error) {
+       return m.GetStageDescSetByDigest(ctx, name, digest, parentTs)
+}
+
+func (m *publicationStorageManager) 
GetStageDescSetByDigestFromStagesStorageWithCache(ctx context.Context, name, 
digest string, parentTs int64, stagesStorage storage.StagesStorage) 
(imagePkg.StageDescSet, error) {
+       return m.GetStageDescSetByDigestFromStagesStorageCached(ctx, name, 
digest, parentTs, stagesStorage)
+}
+
+type secondaryLookupStorageManager struct {
+       *publicationStorageManager
+       lookup    *manager.StorageManager
+       secondary storage.StagesStorage
+}
+
+var _ manager.StorageManagerInterface = (*secondaryLookupStorageManager)(nil)
+
+func (m *secondaryLookupStorageManager) GetSecondaryStagesStorageList() 
[]storage.StagesStorage {
+       return []storage.StagesStorage{m.secondary}
+}
+
+func (m *secondaryLookupStorageManager) 
GetStageDescSetByDigestFromStagesStorageCached(ctx context.Context, name, 
digest string, parentTs int64, stagesStorage storage.StagesStorage) 
(imagePkg.StageDescSet, error) {
+       return m.lookup.GetStageDescSetByDigestFromStagesStorageCached(ctx, 
name, digest, parentTs, stagesStorage)
+}
+
+func (m *secondaryLookupStorageManager) 
GetStageDescSetByDigestFromStagesStorageWithCache(ctx context.Context, name, 
digest string, parentTs int64, stagesStorage storage.StagesStorage) 
(imagePkg.StageDescSet, error) {
+       return m.lookup.GetStageDescSetByDigestFromStagesStorageWithCache(ctx, 
name, digest, parentTs, stagesStorage)
+}
+
+func (m *secondaryLookupStorageManager) CopySuitableStageDescByDigest(_ 
context.Context, desc *imagePkg.StageDesc, _, _ storage.StagesStorage, _ 
container_backend.ContainerBackend, _ string) (*imagePkg.StageDesc, error) {
+       copied := *desc
+       info := *desc.Info
+       info.Name = "repo:shared-digest-100"
+       copied.Info = &info
+       m.primary.desc = &copied
+       m.primary.writes++
+       return &copied, nil
+}
+
+type publicationContainerBackend struct {
+       container_backend.ContainerBackend
+}
+
+var _ container_backend.ContainerBackend = (*publicationContainerBackend)(nil)
+
+func (b *publicationContainerBackend) HasStapelBuildSupport() bool { return 
true }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/pkg/build/require_built_images_test.go 
new/werf-2.84.0/pkg/build/require_built_images_test.go
--- old/werf-2.83.0/pkg/build/require_built_images_test.go      1970-01-01 
01:00:00.000000000 +0100
+++ new/werf-2.84.0/pkg/build/require_built_images_test.go      2026-10-20 
17:16:12.000000000 +0200
@@ -0,0 +1,116 @@
+package build
+
+import (
+       "net/http/httptest"
+       "strings"
+
+       "github.com/google/go-containerregistry/pkg/name"
+       "github.com/google/go-containerregistry/pkg/registry"
+       v1 "github.com/google/go-containerregistry/pkg/v1"
+       "github.com/google/go-containerregistry/pkg/v1/empty"
+       "github.com/google/go-containerregistry/pkg/v1/mutate"
+       "github.com/google/go-containerregistry/pkg/v1/remote"
+       "github.com/onsi/ginkgo/v2"
+       "github.com/onsi/gomega"
+
+       "github.com/werf/werf/v2/pkg/build/stage"
+       "github.com/werf/werf/v2/pkg/config"
+       "github.com/werf/werf/v2/pkg/docker_registry"
+       imagePkg "github.com/werf/werf/v2/pkg/image"
+       "github.com/werf/werf/v2/pkg/storage"
+       "github.com/werf/werf/v2/pkg/storage/manager"
+       "github.com/werf/werf/v2/pkg/werf"
+)
+
+var _ = ginkgo.Describe("Require built images", func() {
+       ginkgo.It("reuses a published stage despite a stale empty stages 
snapshot", func(ctx ginkgo.SpecContext) {
+               server, _ := newPublicationLockServer()
+               primary := &publicationStorage{desc: &imagePkg.StageDesc{
+                       StageID: imagePkg.NewStageID("shared-digest", 100),
+                       Info:    &imagePkg.Info{Name: "repo:shared-digest-100", 
Labels: map[string]string{imagePkg.WerfStageContentDigestLabel: 
"published-content"}},
+               }}
+               storageManager := &publicationStorageManager{primary: primary}
+               phase, img, stg := newPublicationPhase(ctx, storageManager, 
server.URL)
+               phase.Conveyor.StorageManager = 
&requireLookupStorageManager{storageManager}
+               phase.RequireBuiltImages = true
+               reported := &reportedStage{buildableStage: 
&buildableStage{publicationStage: stg.(*publicationStage)}}
+
+               gomega.Expect(phase.onImageStage(ctx, img, 
reported)).To(gomega.Succeed())
+               
gomega.Expect(reported.GetStageImage().Image.GetStageDesc()).To(gomega.Equal(primary.desc))
+               gomega.Expect(primary.writes).To(gomega.BeZero())
+               
gomega.Expect(reported.GetStageImage().Builder.(*stageBuilderStub).builds).To(gomega.BeZero())
+       })
+
+       ginkgo.It("reuses a secondary stage published after the cached empty 
snapshot", func(ctx ginkgo.SpecContext) {
+               gomega.Expect(werf.Init(ginkgo.GinkgoT().TempDir(), 
"")).To(gomega.Succeed())
+               oldCache := imagePkg.CommonManifestCache
+               imagePkg.CommonManifestCache = 
imagePkg.NewManifestCache(ginkgo.GinkgoT().TempDir())
+               ginkgo.DeferCleanup(func() { imagePkg.CommonManifestCache = 
oldCache })
+               registryServer := httptest.NewServer(registry.New())
+               ginkgo.DeferCleanup(registryServer.Close)
+               address := strings.TrimPrefix(registryServer.URL, "http://";) + 
"/project"
+               client, err := docker_registry.NewDockerRegistry(ctx, address, 
docker_registry.DefaultImplementationName, 
docker_registry.DockerRegistryOptions{InsecureRegistry: true})
+               gomega.Expect(err).To(gomega.Succeed())
+               secondary := 
storage.NewRepoStagesStorage(&storage.NewRepoStagesStorageOptions{RepoAddress: 
address, DockerRegistry: client})
+               primary := &publicationStorage{}
+               publicationManager := &publicationStorageManager{primary: 
primary}
+               storageManager := 
&secondaryLookupStorageManager{publicationStorageManager: publicationManager, 
lookup: &manager.StorageManager{ProjectName: "publication-project", 
StagesStorage: secondary}, secondary: secondary}
+               server, _ := newPublicationLockServer()
+               phase, img, stg := newPublicationPhase(ctx, publicationManager, 
server.URL)
+               phase.Conveyor.StorageManager = storageManager
+               phase.RequireBuiltImages = true
+               reported := &reportedStage{buildableStage: 
&buildableStage{publicationStage: stg.(*publicationStage)}}
+               _, cleanup, err := phase.calculateStage(ctx, img, reported)
+               gomega.Expect(err).To(gomega.Succeed())
+               if cleanup != nil {
+                       cleanup()
+               }
+               stageID := *imagePkg.NewStageID(reported.GetDigest(), 
1611836746968)
+               cached, err := 
storageManager.GetStageDescSetByDigestFromStagesStorageCached(ctx, "stage", 
stageID.Digest, 10, secondary)
+               gomega.Expect(err).To(gomega.Succeed())
+               gomega.Expect(cached.IsEmpty()).To(gomega.BeTrue())
+               containerImage, err := mutate.ConfigFile(empty.Image, 
&v1.ConfigFile{OS: "linux", Architecture: "amd64", Config: v1.Config{Labels: 
map[string]string{imagePkg.WerfStageContentDigestLabel: "published-content", 
imagePkg.WerfLabel: "publication-project"}}})
+               gomega.Expect(err).To(gomega.Succeed())
+               tag, err := 
name.NewTag(secondary.ConstructStageImageName("publication-project", 
stageID.Digest, stageID.CreationTs))
+               gomega.Expect(err).To(gomega.Succeed())
+               gomega.Expect(remote.Write(tag, containerImage, 
remote.WithContext(ctx))).To(gomega.Succeed())
+               cached, err = 
storageManager.GetStageDescSetByDigestFromStagesStorageCached(ctx, "stage", 
stageID.Digest, 10, secondary)
+               gomega.Expect(err).To(gomega.Succeed())
+               gomega.Expect(cached.IsEmpty()).To(gomega.BeTrue())
+
+               gomega.Expect(phase.onImageStage(ctx, img, 
reported)).To(gomega.Succeed())
+               gomega.Expect(primary.writes).To(gomega.Equal(1))
+               gomega.Expect(primary.desc.StageID).To(gomega.Equal(&stageID))
+               
gomega.Expect(reported.GetContentDigest()).To(gomega.Equal("published-content"))
+               
gomega.Expect(reported.GetStageImage().Builder.(*stageBuilderStub).builds).To(gomega.BeZero())
+       })
+
+       ginkgo.DescribeTable("does not build a missing stage when built images 
are required",
+               func(ctx ginkgo.SpecContext, require, dockerfile bool) {
+                       server, _ := newPublicationLockServer()
+                       primary := &publicationStorage{}
+                       phase, img, stg := newPublicationPhase(ctx, 
&publicationStorageManager{primary: primary}, server.URL)
+                       phase.RequireBuiltImages = require
+                       phase.Conveyor.ContainerBackend = 
&publicationContainerBackend{}
+                       img.IsDockerfileImage = dockerfile
+                       img.DockerfileImageConfig = 
&config.ImageFromDockerfile{}
+                       reported := &reportedStage{buildableStage: 
&buildableStage{publicationStage: stg.(*publicationStage)}}
+                       img.SetStages([]stage.Interface{reported})
+
+                       err := phase.onImageStage(ctx, img, reported)
+                       if require {
+                               
gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("build images 
first without --require-built-images")))
+                               
gomega.Expect(primary.writes).To(gomega.BeZero())
+                               
gomega.Expect(reported.GetStageImage().Builder).To(gomega.BeNil())
+                       } else {
+                               gomega.Expect(err).To(gomega.Succeed())
+                               
gomega.Expect(primary.writes).To(gomega.Equal(1))
+                               
gomega.Expect(reported.GetStageImage().Builder.(*stageBuilderStub).builds).To(gomega.Equal(1))
+                       }
+               },
+               ginkgo.Entry("require fails before invoking the Dockerfile 
builder", true, true),
+               ginkgo.Entry("require fails before invoking the Stapel 
builder", true, false),
+               ginkgo.Entry("ordinary Dockerfile build invokes the builder and 
publishes the stage", false, true),
+               ginkgo.Entry("ordinary Stapel build invokes the builder and 
publishes the stage", false, false),
+       )
+})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/pkg/storage/manager/helpers_test.go 
new/werf-2.84.0/pkg/storage/manager/helpers_test.go
--- old/werf-2.83.0/pkg/storage/manager/helpers_test.go 2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/pkg/storage/manager/helpers_test.go 2026-10-20 
17:16:12.000000000 +0200
@@ -13,12 +13,28 @@
        "github.com/onsi/ginkgo/v2"
        "github.com/onsi/gomega"
 
+       "github.com/werf/werf/v2/pkg/build/stage"
        "github.com/werf/werf/v2/pkg/container_backend"
        "github.com/werf/werf/v2/pkg/docker_registry"
        "github.com/werf/werf/v2/pkg/image"
        "github.com/werf/werf/v2/pkg/storage"
 )
 
+type fetchStage struct {
+       stage.Interface
+       stageImage *stage.StageImage
+}
+
+var _ stage.Interface = (*fetchStage)(nil)
+
+func (s *fetchStage) GetStageImage() *stage.StageImage {
+       return s.stageImage
+}
+
+func (s *fetchStage) LogDetailedName() string {
+       return "stage"
+}
+
 type lookupBackend struct {
        container_backend.ContainerBackend
        images image.ImagesList
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.83.0/pkg/storage/manager/storage_manager.go 
new/werf-2.84.0/pkg/storage/manager/storage_manager.go
--- old/werf-2.83.0/pkg/storage/manager/storage_manager.go      2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/pkg/storage/manager/storage_manager.go      2026-10-20 
17:16:12.000000000 +0200
@@ -509,7 +509,7 @@
 
                        stageDesc, err := getStageDesc(ctx, m.ProjectName, 
*stageID, stagesStorage, nil, getStageDescOptions{WithLocalManifestCache: true})
                        if err != nil {
-                               return nil, fmt.Errorf("error getting stage %s 
description from %s: %w", stageID.String(), m.FinalStagesStorage.String(), err)
+                               return nil, fmt.Errorf("error getting stage %s 
description from %s: %w", stageID.String(), stagesStorage.String(), err)
                        }
                        pulled = false
                        stageImage.SetStageDesc(stageDesc)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/werf-2.83.0/pkg/storage/manager/storage_manager_fetch_test.go 
new/werf-2.84.0/pkg/storage/manager/storage_manager_fetch_test.go
--- old/werf-2.83.0/pkg/storage/manager/storage_manager_fetch_test.go   
1970-01-01 01:00:00.000000000 +0100
+++ new/werf-2.84.0/pkg/storage/manager/storage_manager_fetch_test.go   
2026-10-20 17:16:12.000000000 +0200
@@ -0,0 +1,101 @@
+package manager
+
+import (
+       "bytes"
+       "fmt"
+       "net/http"
+       "net/http/httptest"
+       "strings"
+
+       "github.com/google/go-containerregistry/pkg/name"
+       "github.com/google/go-containerregistry/pkg/v1/empty"
+       "github.com/google/go-containerregistry/pkg/v1/remote"
+       "github.com/onsi/ginkgo/v2"
+       "github.com/onsi/gomega"
+       "go.uber.org/mock/gomock"
+
+       "github.com/werf/logboek"
+       "github.com/werf/logboek/pkg/level"
+       "github.com/werf/werf/v2/pkg/build/stage"
+       "github.com/werf/werf/v2/pkg/container_backend"
+       "github.com/werf/werf/v2/pkg/docker_registry"
+       "github.com/werf/werf/v2/pkg/image"
+       "github.com/werf/werf/v2/pkg/storage"
+       "github.com/werf/werf/v2/pkg/werf"
+       "github.com/werf/werf/v2/test/mock"
+)
+
+var _ = ginkgo.DescribeTable("FetchStage with a locally available cache image",
+       func(ctx ginkgo.SpecContext, withFinalRepo bool) {
+               var output bytes.Buffer
+               logger := logboek.NewLogger(&output, &output)
+               logger.SetAcceptedLevel(level.Warn)
+               logger.Streams().DisableStyle()
+               logger.Streams().DisableLineWrapping()
+               logCtx := logboek.NewContext(ctx, logger)
+               gomega.Expect(werf.Init(ginkgo.GinkgoT().TempDir(), 
"")).To(gomega.Succeed())
+               previousCache := image.CommonManifestCache
+               image.CommonManifestCache = 
image.NewManifestCache(ginkgo.GinkgoT().TempDir())
+               ginkgo.DeferCleanup(func() { image.CommonManifestCache = 
previousCache })
+
+               cacheServer := httptest.NewServer(http.HandlerFunc(func(writer 
http.ResponseWriter, request *http.Request) {
+                       if request.URL.Path == "/v2/" {
+                               writer.WriteHeader(http.StatusOK)
+                               return
+                       }
+                       http.Error(writer, "cache descriptor access denied", 
http.StatusForbidden)
+               }))
+               ginkgo.DeferCleanup(cacheServer.Close)
+               cacheAddress := strings.TrimPrefix(cacheServer.URL, "http://";) 
+ "/cache"
+               cacheRegistry, err := docker_registry.NewDockerRegistry(logCtx, 
cacheAddress, docker_registry.DefaultImplementationName, 
docker_registry.DockerRegistryOptions{InsecureRegistry: true})
+               gomega.Expect(err).To(gomega.Succeed())
+
+               backend := 
mock.NewMockContainerBackend(gomock.NewController(ginkgo.GinkgoT()))
+               cache := 
storage.NewRepoStagesStorage(&storage.NewRepoStagesStorageOptions{
+                       RepoAddress: cacheAddress, DockerRegistry: 
cacheRegistry, ContainerBackend: backend,
+               })
+               manager := newManifestStorageManager(logCtx)
+               
manager.StagesStorage.(*storage.RepoStagesStorage).ContainerBackend = backend
+               manager.CacheStagesStorageList = []storage.StagesStorage{cache}
+               if withFinalRepo {
+                       manager.FinalStagesStorage = 
storage.NewRepoStagesStorage(&storage.NewRepoStagesStorageOptions{RepoAddress: 
"registry.example/final"})
+               }
+               ginkgo.DeferCleanup(func() {
+                       for _, lock := range manager.SharedHostImagesLocks {
+                               
gomega.Expect(werf.HostLocker().ReleaseLock(lock)).To(gomega.Succeed())
+                       }
+               })
+
+               stageID := image.NewStageID(missingStageDigest, 100)
+               primaryName := 
manager.StagesStorage.ConstructStageImageName(manager.ProjectName, 
stageID.Digest, stageID.CreationTs)
+               primaryRef, err := name.NewTag(primaryName, name.Insecure)
+               gomega.Expect(err).To(gomega.Succeed())
+               gomega.Expect(remote.Write(primaryRef, empty.Image, 
remote.WithContext(ctx))).To(gomega.Succeed())
+               primaryImage := container_backend.NewLegacyStageImage(nil, 
primaryName, backend, "linux/amd64")
+               primaryImage.SetStageDesc(&image.StageDesc{StageID: stageID})
+               cacheName := cache.ConstructStageImageName(manager.ProjectName, 
stageID.Digest, stageID.CreationTs)
+               gomock.InOrder(
+                       backend.EXPECT().GetImageInfo(gomock.Any(), 
primaryName, container_backend.GetImageInfoOpts{TargetPlatform: 
"linux/amd64"}).Return(nil, nil),
+                       backend.EXPECT().GetImageInfo(gomock.Any(), cacheName, 
container_backend.GetImageInfoOpts{TargetPlatform: 
"linux/amd64"}).Return(&image.Info{Name: cacheName}, nil),
+                       backend.EXPECT().PullImageFromRegistry(gomock.Any(), 
primaryImage).Return(nil),
+               )
+
+               var info FetchStageInfo
+               gomega.Expect(func() {
+                       info, err = manager.FetchStage(logCtx, backend, 
&fetchStage{stageImage: &stage.StageImage{Image: primaryImage}})
+               }).NotTo(gomega.Panic())
+               gomega.Expect(err).To(gomega.Succeed())
+               
gomega.Expect(info.BaseImageSource).To(gomega.Equal(BaseImageSourceTypeRepo))
+               
gomega.Expect(primaryImage.GetStageDesc().Info.Name).To(gomega.Equal(primaryName))
+               gomega.Expect(strings.Split(output.String(), 
"\n")).To(gomega.ContainElement(gomega.And(
+                       gomega.HavePrefix(fmt.Sprintf(
+                               "Unable to fetch stage %s from cache stages 
storage %s: error getting stage %s description from %s:",
+                               stageID.String(), cache.String(), 
stageID.String(), cache.String(),
+                       )),
+                       gomega.ContainSubstring("cache descriptor access 
denied"),
+               )))
+               
gomega.Expect(output.String()).NotTo(gomega.ContainSubstring("registry.example/final"))
+       },
+       ginkgo.Entry("warns and falls back after a descriptor error without 
final-repo", false),
+       ginkgo.Entry("reports the cache address rather than final-repo after a 
descriptor error", true),
+)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/werf-2.83.0/test/e2e/build/check_built_images_test.go 
new/werf-2.84.0/test/e2e/build/check_built_images_test.go
--- old/werf-2.83.0/test/e2e/build/check_built_images_test.go   2026-10-15 
12:45:06.000000000 +0200
+++ new/werf-2.84.0/test/e2e/build/check_built_images_test.go   2026-10-20 
17:16:12.000000000 +0200
@@ -5,17 +5,26 @@
        "os/exec"
        "slices"
 
+       "github.com/google/go-containerregistry/pkg/name"
+       "github.com/google/go-containerregistry/pkg/v1/remote"
        . "github.com/onsi/ginkgo/v2"
        . "github.com/onsi/gomega"
 
+       "github.com/werf/werf/v2/test/pkg/suite_init"
        "github.com/werf/werf/v2/test/pkg/utils"
        "github.com/werf/werf/v2/test/pkg/werf"
 )
 
 type checkBuiltImagesTestOptions struct {
        setupEnvOptions
-       Args []string
-       Env  map[string]string
+       Args    []string
+       Env     map[string]string
+       Require bool
+}
+
+var checkBuiltImagesEnv = setupEnvOptions{
+       ContainerBackendMode: "docker",
+       WithLocalRepo:        true,
 }
 
 var _ = Describe("Check built images", Label("e2e", "build", "simple"), func() 
{
@@ -45,7 +54,11 @@
                        var checkExitErr *exec.ExitError
                        Expect(errors.As(checkErr, &checkExitErr)).To(BeTrue(), 
"expected werf build to exit with a non-zero status, got: %v", checkErr)
                        checkOut := string(checkOutBytes)
-                       Expect(checkOut).To(ContainSubstring("stages required"))
+                       if opts.Require {
+                               Expect(checkOut).To(ContainSubstring("build 
images first without --require-built-images"))
+                       } else {
+                               Expect(checkOut).To(ContainSubstring("stages 
required"))
+                       }
                        Expect(checkOut).NotTo(ContainSubstring("Building 
stage"))
 
                        By("state0: building images")
@@ -65,51 +78,74 @@
                        Expect(checkOut).NotTo(ContainSubstring("Building 
stage"))
                },
                Entry("with --check-built-images", checkBuiltImagesTestOptions{
-                       setupEnvOptions: setupEnvOptions{
-                               ContainerBackendMode: "vanilla-docker",
-                               WithLocalRepo:        true,
-                       },
-                       Args: []string{"--check-built-images"},
+                       setupEnvOptions: checkBuiltImagesEnv,
+                       Args:            []string{"--check-built-images"},
                }),
                Entry("with --require-built-images", 
checkBuiltImagesTestOptions{
-                       setupEnvOptions: setupEnvOptions{
-                               ContainerBackendMode: "vanilla-docker",
-                               WithLocalRepo:        true,
-                       },
-                       Args: []string{"--require-built-images"},
+                       setupEnvOptions: checkBuiltImagesEnv,
+                       Args:            []string{"--require-built-images"},
+                       Require:         true,
+               }),
+               Entry("with --require-built-images for Stapel only", 
checkBuiltImagesTestOptions{
+                       setupEnvOptions: checkBuiltImagesEnv,
+                       Args:            []string{"stapel-shell", 
"--require-built-images"},
+                       Require:         true,
+               }),
+               Entry("with --require-built-images for Stapel only using 
Buildah", checkBuiltImagesTestOptions{
+                       setupEnvOptions: setupEnvOptions{ContainerBackendMode: 
"native-rootless", WithLocalRepo: true},
+                       Args:            []string{"stapel-shell", 
"--require-built-images"},
+                       Require:         true,
                }),
                Entry("with -Z", checkBuiltImagesTestOptions{
-                       setupEnvOptions: setupEnvOptions{
-                               ContainerBackendMode: "vanilla-docker",
-                               WithLocalRepo:        true,
-                       },
-                       Args: []string{"-Z"},
+                       setupEnvOptions: checkBuiltImagesEnv,
+                       Args:            []string{"-Z"},
+                       Require:         true,
                }),
                Entry("with WERF_CHECK_BUILT_IMAGES", 
checkBuiltImagesTestOptions{
-                       setupEnvOptions: setupEnvOptions{
-                               ContainerBackendMode: "vanilla-docker",
-                               WithLocalRepo:        true,
-                       },
-                       Env: map[string]string{"WERF_CHECK_BUILT_IMAGES": "1"},
+                       setupEnvOptions: checkBuiltImagesEnv,
+                       Env:             
map[string]string{"WERF_CHECK_BUILT_IMAGES": "1"},
+               }),
+               Entry("with WERF_REQUIRE_BUILT_IMAGES", 
checkBuiltImagesTestOptions{
+                       setupEnvOptions: checkBuiltImagesEnv,
+                       Env:             
map[string]string{"WERF_REQUIRE_BUILT_IMAGES": "1"},
+                       Require:         true,
                }),
        )
+})
 
-       It("should build images regardless of WERF_REQUIRE_BUILT_IMAGES", 
func(ctx SpecContext) {
-               By("initializing")
-               setupEnv(setupEnvOptions{
-                       ContainerBackendMode: "vanilla-docker",
-                       WithLocalRepo:        true,
-               })
-               SuiteData.Stubs.SetEnv("WERF_REQUIRE_BUILT_IMAGES", "1")
-
-               By("state0: preparing test repo")
-               repoDirname := "repo0"
-               SuiteData.InitTestRepo(ctx, repoDirname, "simple/state0")
-               werfProject := werf.NewProject(SuiteData.WerfBinPath, 
SuiteData.GetTestRepoPath(repoDirname))
-
-               By("state0: building images against an empty repo")
-               buildOut := werfProject.Build(ctx, nil)
-               Expect(buildOut).To(ContainSubstring("Building stage"))
-               Expect(buildOut).NotTo(ContainSubstring("stages required"))
-       })
+var _ = Describe("Require built images publication", Label("e2e", "build", 
"simple"), func() {
+       DescribeTable("copies secondary stages and publishes the final 
repository without building", func(ctx SpecContext, opts setupEnvOptions) {
+               setupEnv(opts)
+               SuiteData.InitTestRepo(ctx, "repo0", "simple/state0")
+               project := werf.NewProject(SuiteData.WerfBinPath, 
SuiteData.GetTestRepoPath("repo0"))
+               project.Build(ctx, &werf.BuildOptions{CommonOptions: 
werf.CommonOptions{ExtraArgs: []string{"dockerfile"}}})
+
+               source := suite_init.TestRepo(SuiteData.ProjectName)
+               primary := suite_init.TestRepo(SuiteData.ProjectName + 
"-required")
+               finalRepo := suite_init.TestRepo(SuiteData.ProjectName + 
"-final")
+               args := []string{"dockerfile", "--repo", primary, 
"--secondary-repo", source, "--add-custom-tag", "release"}
+               args = append(args, "--final-repo", finalRepo)
+
+               checkOutput := project.Build(ctx, 
&werf.BuildOptions{CommonOptions: werf.CommonOptions{
+                       ShouldFail: true,
+                       ExtraArgs:  slices.Concat(args, 
[]string{"--check-built-images"}),
+               }})
+               Expect(checkOutput).To(ContainSubstring("stages required"))
+
+               output := project.Build(ctx, &werf.BuildOptions{CommonOptions: 
werf.CommonOptions{
+                       ExtraArgs: slices.Concat(args, 
[]string{"--require-built-images"}),
+               }})
+               Expect(output).NotTo(ContainSubstring("Building stage"))
+               tag, err := name.NewTag(finalRepo+":release", name.Insecure)
+               Expect(err).NotTo(HaveOccurred())
+               manifest, err := remote.Get(tag, remote.WithContext(ctx))
+               Expect(err).NotTo(HaveOccurred())
+               Expect(manifest.Digest.String()).NotTo(BeEmpty())
+               project.Build(ctx, &werf.BuildOptions{CommonOptions: 
werf.CommonOptions{
+                       ExtraArgs: slices.Concat(args, 
[]string{"--check-built-images"}),
+               }})
+       },
+               Entry("Docker", setupEnvOptions{ContainerBackendMode: "docker", 
WithLocalRepo: true}),
+               Entry("Buildah", setupEnvOptions{ContainerBackendMode: 
"native-rootless", WithLocalRepo: true}),
+       )
 })

++++++ werf.obsinfo ++++++
--- /var/tmp/diff_new_pack.poTGC7/_old  2026-11-13 18:39:01.941698681 +0100
+++ /var/tmp/diff_new_pack.poTGC7/_new  2026-11-13 18:39:01.946698893 +0100
@@ -1,5 +1,5 @@
 name: werf
-version: 2.83.0
-mtime: 1792061106
-commit: 393e5d5e9fe4bea56b226174e3d4397bc78ce602
+version: 2.84.0
+mtime: 1792509372
+commit: 4ca847c4b39ff5174f9174b41613133f4dfdcd62
 

Reply via email to