From 1845a6d89000e997d350b1dc1c8eeb19dbba69db Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 1 Jul 2026 13:38:02 +0800 Subject: [PATCH 1/4] feat: add an OCI registry backend selectable via OCI_REGISTRY buildRegistry returns a cocoon-common oci.Registry client when OCI_REGISTRY is set (GCP ADC then docker config), else the epoch registryclient backend, so the reconcilers' manifest probe + rollback run against Artifact Registry without epoch (issue #25). epoch is now imported only for the transitional backend. --- go.mod | 22 +++++++++++------ go.sum | 56 ++++++++++++++++++++++++++++---------------- main.go | 26 ++++++++++++++++---- main_test.go | 30 ++++++++++++++++++++++++ snapshot/registry.go | 6 ++--- 5 files changed, 106 insertions(+), 34 deletions(-) create mode 100644 main_test.go diff --git a/go.mod b/go.mod index fd61e04..33a933e 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,13 @@ module github.com/cocoonstack/cocoon-operator go 1.25.6 require ( - github.com/cocoonstack/cocoon-common v0.2.2 + github.com/cocoonstack/cocoon-common v0.2.3-0.20260701050723-6e675f810e8d github.com/cocoonstack/epoch v0.2.4 github.com/go-logr/logr v1.4.3 + github.com/google/go-containerregistry v0.21.7 github.com/projecteru2/core v0.0.0-20241016125006-ff909eefe04c github.com/prometheus/client_golang v1.23.2 - golang.org/x/sync v0.19.0 + golang.org/x/sync v0.21.0 k8s.io/api v0.35.3 k8s.io/apimachinery v0.35.3 k8s.io/client-go v0.35.3 @@ -16,6 +17,7 @@ require ( ) require ( + cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/alphadose/haxmap v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -23,6 +25,8 @@ require ( github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/redact v1.1.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/cli v29.5.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -38,6 +42,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.18.6 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -47,6 +52,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -54,16 +61,17 @@ require ( github.com/prometheus/procfs v0.16.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/zerolog v1.29.1 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.38.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.9.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/grpc v1.72.2 // indirect diff --git a/go.sum b/go.sum index 217d411..960f749 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= @@ -27,8 +29,8 @@ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZe github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cocoonstack/cocoon-common v0.2.2 h1:+qY4Iv1nLZw1brH46j33Kvajqi8UR1QKLLTC/AXhU30= -github.com/cocoonstack/cocoon-common v0.2.2/go.mod h1:xIXbJ83vngQ2mrLC6q0Tw7h21M9BYBBqqYTcHaUrm1Y= +github.com/cocoonstack/cocoon-common v0.2.3-0.20260701050723-6e675f810e8d h1:8IAri8O+Sy6r+JDONULPgXx6YULohMFh05108uJLbpg= +github.com/cocoonstack/cocoon-common v0.2.3-0.20260701050723-6e675f810e8d/go.mod h1:/Cf3aBBN0blBxJWexuGuMbTkas+scvQiF2I75aQXkH4= github.com/cocoonstack/epoch v0.2.4 h1:X18DdsWlPRqEVswR1N3JAazxRZfBUtoL5r7GTgWmhN8= github.com/cocoonstack/epoch v0.2.4/go.mod h1:1bFeUom4aeFbw3vjFbrLegEgVShqe4qSOfpmH5UEpww= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= @@ -43,6 +45,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/docker/cli v29.5.3+incompatible h1:nbEFfz774vBwQ5KRYv7c/AghjReqnGISvrRhzjV0evs= +github.com/docker/cli v29.5.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= @@ -126,6 +132,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU= +github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -165,8 +173,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -228,6 +236,10 @@ github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zw github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= @@ -261,6 +273,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -338,8 +352,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -357,11 +371,11 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -369,8 +383,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -394,19 +408,19 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -423,8 +437,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= +golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -487,6 +501,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= diff --git a/main.go b/main.go index 92d91d0..8a51466 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,8 @@ import ( "syscall" "github.com/go-logr/logr" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/v1/google" "github.com/projecteru2/core/log" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -27,9 +29,11 @@ import ( cocoonv1 "github.com/cocoonstack/cocoon-common/apis/v1" commonk8s "github.com/cocoonstack/cocoon-common/k8s" commonlog "github.com/cocoonstack/cocoon-common/log" + "github.com/cocoonstack/cocoon-common/oci" "github.com/cocoonstack/cocoon-operator/cocoonset" "github.com/cocoonstack/cocoon-operator/hibernation" "github.com/cocoonstack/cocoon-operator/metrics" + "github.com/cocoonstack/cocoon-operator/snapshot" "github.com/cocoonstack/cocoon-operator/version" "github.com/cocoonstack/epoch/registryclient" ) @@ -87,9 +91,9 @@ func main() { logger.Fatalf(ctx, err, "add readyz check: %v", err) } - epochClient, err := registryclient.NewFromEnv(commonk8s.EnvOrDefault("EPOCH_URL", "http://epoch.cocoon-system.svc:8080"), os.Getenv("EPOCH_TOKEN")) + registry, err := buildRegistry() if err != nil { - logger.Fatalf(ctx, err, "create epoch client: %v", err) + logger.Fatalf(ctx, err, "create registry client: %v", err) } metrics.Register() @@ -106,7 +110,7 @@ func main() { if err = (&cocoonset.Reconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Epoch: epochClient, + Epoch: registry, Recorder: recorder, }).SetupWithManager(ctx, mgr); err != nil { logger.Fatalf(ctx, err, "register cocoonset.Reconciler: %v", err) @@ -114,7 +118,7 @@ func main() { if err = (&hibernation.Reconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Epoch: epochClient, + Epoch: registry, Recorder: recorder, }).SetupWithManager(ctx, mgr); err != nil { logger.Fatalf(ctx, err, "register hibernation.Reconciler: %v", err) @@ -130,6 +134,20 @@ func main() { } } +// buildRegistry selects the registry backend: an OCI registry when OCI_REGISTRY +// is set (GCP ADC then docker config), else the epoch backend. +func buildRegistry() (snapshot.Registry, error) { + if base := os.Getenv("OCI_REGISTRY"); base != "" { + keychain := authn.NewMultiKeychain(google.Keychain, authn.DefaultKeychain) + return oci.NewOCIRegistry(base, keychain), nil + } + client, err := registryclient.NewFromEnv(commonk8s.EnvOrDefault("EPOCH_URL", "http://epoch.cocoon-system.svc:8080"), os.Getenv("EPOCH_TOKEN")) + if err != nil { + return nil, err + } + return client, nil +} + func buildScheme() *runtime.Scheme { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..7417594 --- /dev/null +++ b/main_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "testing" + + "github.com/cocoonstack/cocoon-common/oci" + "github.com/cocoonstack/epoch/registryclient" +) + +func TestBuildRegistryBackend(t *testing.T) { + t.Setenv("EPOCH_CA_CERT", "") // keep the epoch client deterministic on dev machines + + t.Setenv("OCI_REGISTRY", "example.com/proj/repo") + reg, err := buildRegistry() + if err != nil { + t.Fatalf("buildRegistry(OCI): %v", err) + } + if _, ok := reg.(*oci.OCIRegistry); !ok { + t.Fatalf("OCI_REGISTRY set: got %T, want *oci.OCIRegistry", reg) + } + + t.Setenv("OCI_REGISTRY", "") + ep, err := buildRegistry() + if err != nil { + t.Fatalf("buildRegistry(epoch): %v", err) + } + if _, ok := ep.(*registryclient.Client); !ok { + t.Fatalf("no OCI_REGISTRY: got %T, want *registryclient.Client", ep) + } +} diff --git a/snapshot/registry.go b/snapshot/registry.go index a2c6094..b2929f2 100644 --- a/snapshot/registry.go +++ b/snapshot/registry.go @@ -4,9 +4,9 @@ package snapshot import "context" -// Registry is the subset of epoch's HTTP API the operator reconcilers need. -// *registryclient.Client from the epoch module satisfies it natively; tests -// swap in fakes. +// Registry is the subset of registry operations the operator reconcilers need. +// Both cocoon-common's oci.OCIRegistry and epoch's registryclient.Client +// satisfy it; tests swap in fakes. type Registry interface { HasManifest(ctx context.Context, name, reference string) (bool, error) DeleteManifest(ctx context.Context, name, reference string) error From fbec175ce355b29c13d6ee2081e6a25cb7e36dc3 Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 1 Jul 2026 13:39:34 +0800 Subject: [PATCH 2/4] refactor: rename the reconciler Epoch field to Registry The field now holds either an OCI client or the epoch backend, so Epoch is misleading; Registry matches its snapshot.Registry type and the vk naming. Mechanical rename, no behavior change. --- cocoonset/delete.go | 6 +++--- cocoonset/reconciler.go | 2 +- cocoonset/reconciler_test.go | 14 +++++++------- cocoonset/suspend.go | 4 ++-- hibernation/hibernate.go | 2 +- hibernation/reconciler.go | 6 +++--- hibernation/reconciler_test.go | 34 +++++++++++++++++----------------- hibernation/wake.go | 2 +- main.go | 4 ++-- 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/cocoonset/delete.go b/cocoonset/delete.go index d7c853d..ade6e32 100644 --- a/cocoonset/delete.go +++ b/cocoonset/delete.go @@ -59,15 +59,15 @@ func (r *Reconciler) reconcileDelete(ctx context.Context, cs *cocoonv1.CocoonSet // :hibernate is always orphaned at teardown — drop unconditionally. :latest // is kept when shouldKeepLatestTag says vk-cocoon pushed it for retag. - if r.Epoch != nil { + if r.Registry != nil { for _, name := range vmNamesForGC(cs) { - if err := r.Epoch.DeleteManifest(ctx, name, meta.HibernateSnapshotTag); err != nil { + if err := r.Registry.DeleteManifest(ctx, name, meta.HibernateSnapshotTag); err != nil { logger.Warnf(ctx, "delete snapshot %s:%s: %v", name, meta.HibernateSnapshotTag, err) } if shouldKeepLatestTag(cs, name) { continue } - if err := r.Epoch.DeleteManifest(ctx, name, meta.DefaultSnapshotTag); err != nil { + if err := r.Registry.DeleteManifest(ctx, name, meta.DefaultSnapshotTag); err != nil { logger.Warnf(ctx, "delete snapshot %s:%s: %v", name, meta.DefaultSnapshotTag, err) } } diff --git a/cocoonset/reconciler.go b/cocoonset/reconciler.go index 2ed8a9f..3c2baef 100644 --- a/cocoonset/reconciler.go +++ b/cocoonset/reconciler.go @@ -33,7 +33,7 @@ const ( type Reconciler struct { client.Client Scheme *runtime.Scheme - Epoch snapshot.Registry + Registry snapshot.Registry Recorder record.EventRecorder } diff --git a/cocoonset/reconciler_test.go b/cocoonset/reconciler_test.go index c89ee08..7093d42 100644 --- a/cocoonset/reconciler_test.go +++ b/cocoonset/reconciler_test.go @@ -216,7 +216,7 @@ func TestAllOwnedPodsHibernatedWaitsForEachManagedPod(t *testing.T) { reg := &fakeRegistry{present: map[string]bool{ "vk-ns-demo-0:" + meta.HibernateSnapshotTag: true, }} - r := &Reconciler{Scheme: scheme, Epoch: reg} + r := &Reconciler{Scheme: scheme, Registry: reg} done, err := r.allOwnedPodsHibernated(t.Context(), classified) if err != nil { @@ -255,7 +255,7 @@ func TestAllOwnedPodsHibernatedSkipsUnmanagedToolbox(t *testing.T) { reg := &fakeRegistry{present: map[string]bool{ "vk-ns-demo-0:" + meta.HibernateSnapshotTag: true, }} - r := &Reconciler{Scheme: scheme, Epoch: reg} + r := &Reconciler{Scheme: scheme, Registry: reg} done, err := r.allOwnedPodsHibernated(t.Context(), classified) if err != nil { @@ -276,7 +276,7 @@ func TestAllOwnedPodsHibernatedPropagatesProbeError(t *testing.T) { toolbox: map[string]*corev1.Pod{}, allByName: map[string]*corev1.Pod{main.Name: main}, } - r := &Reconciler{Scheme: scheme, Epoch: &fakeRegistry{probeErr: errors.New("transport boom")}} + r := &Reconciler{Scheme: scheme, Registry: &fakeRegistry{probeErr: errors.New("transport boom")}} if _, err := r.allOwnedPodsHibernated(t.Context(), classified); err == nil { t.Fatal("expected probe error to surface") } @@ -363,7 +363,7 @@ func TestReconcileMainLifecycleFailedTransitionsToFailed(t *testing.T) { WithObjects(cs, mainPod). WithStatusSubresource(&cocoonv1.CocoonSet{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} if _, err := r.Reconcile(t.Context(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: cs.Namespace, Name: cs.Name}}); err != nil { t.Fatalf("Reconcile: %v", err) @@ -557,7 +557,7 @@ func TestReconcileDeleteSnapshotPolicyGC(t *testing.T) { cli := ctrlfake.NewClientBuilder().WithScheme(scheme).WithObjects(cs).Build() reg := &fakeRegistry{} - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: reg} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: reg} if _, err := r.reconcileDelete(t.Context(), cs); err != nil { t.Fatalf("reconcileDelete: %v", err) @@ -583,7 +583,7 @@ func TestReconcileDeleteStashesPodVMNamesEvenWhenStatusIsEmpty(t *testing.T) { WithObjects(cs, mustBuildAgentPod(t, cs, 0, "", "", scheme)). Build() reg := &fakeRegistry{} - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: reg} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: reg} if _, err := r.reconcileDelete(t.Context(), cs); err != nil { t.Fatalf("reconcileDelete: %v", err) @@ -611,7 +611,7 @@ func TestReconcileDeleteCleansTagsAfterPodsGone(t *testing.T) { cli := ctrlfake.NewClientBuilder().WithScheme(scheme).WithObjects(cs).Build() reg := &fakeRegistry{} - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: reg} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: reg} if _, err := r.reconcileDelete(t.Context(), cs); err != nil { t.Fatalf("reconcileDelete: %v", err) diff --git a/cocoonset/suspend.go b/cocoonset/suspend.go index b96e2c4..3097679 100644 --- a/cocoonset/suspend.go +++ b/cocoonset/suspend.go @@ -56,7 +56,7 @@ func (r *Reconciler) reconcileSuspend(ctx context.Context, cs *cocoonv1.CocoonSe // Returns (false, nil) whenever the expected state is not yet observed so // the caller requeues rather than treats it as an error. func (r *Reconciler) allOwnedPodsHibernated(ctx context.Context, classified classifiedPods) (bool, error) { - if r.Epoch == nil { + if r.Registry == nil { // No registry configured; epoch-less deployments have no snapshot to // observe, so treat the annotation write as authoritative. return true, nil @@ -73,7 +73,7 @@ func (r *Reconciler) allOwnedPodsHibernated(ctx context.Context, classified clas if spec.VMName == "" { return false, nil } - present, err := r.Epoch.HasManifest(ctx, spec.VMName, meta.HibernateSnapshotTag) + present, err := r.Registry.HasManifest(ctx, spec.VMName, meta.HibernateSnapshotTag) if err != nil { return false, fmt.Errorf("probe hibernate snapshot %s: %w", spec.VMName, err) } diff --git a/hibernation/hibernate.go b/hibernation/hibernate.go index 31f78a2..ad099eb 100644 --- a/hibernation/hibernate.go +++ b/hibernation/hibernate.go @@ -19,7 +19,7 @@ func (r *Reconciler) reconcileHibernate(ctx context.Context, hib *cocoonv1.Cocoo return ctrl.Result{}, fmt.Errorf("patch hibernate annotation: %w", err) } - present, err := r.Epoch.HasManifest(ctx, vmName, meta.HibernateSnapshotTag) + present, err := r.Registry.HasManifest(ctx, vmName, meta.HibernateSnapshotTag) if err != nil { return ctrl.Result{}, fmt.Errorf("probe hibernate snapshot %s: %w", vmName, err) } diff --git a/hibernation/reconciler.go b/hibernation/reconciler.go index 0c4cb42..48ff36a 100644 --- a/hibernation/reconciler.go +++ b/hibernation/reconciler.go @@ -54,7 +54,7 @@ const ( type Reconciler struct { client.Client Scheme *runtime.Scheme - Epoch snapshot.Registry + Registry snapshot.Registry Recorder record.EventRecorder // observed[UID] = last recorded Ready.LastTransitionTime, dedups @@ -150,8 +150,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // reconcileDelete clears the :hibernate tag (if Status.VMName is set) and removes the finalizer. func (r *Reconciler) reconcileDelete(ctx context.Context, hib *cocoonv1.CocoonHibernation) (ctrl.Result, error) { logger := log.WithFunc("hibernation.Reconciler.reconcileDelete") - if r.Epoch != nil && hib.Status.VMName != "" { - if err := r.Epoch.DeleteManifest(ctx, hib.Status.VMName, meta.HibernateSnapshotTag); err != nil { + if r.Registry != nil && hib.Status.VMName != "" { + if err := r.Registry.DeleteManifest(ctx, hib.Status.VMName, meta.HibernateSnapshotTag); err != nil { logger.Warnf(ctx, "delete hibernate snapshot %s: %v", hib.Status.VMName, err) } } diff --git a/hibernation/reconciler_test.go b/hibernation/reconciler_test.go index 5569705..cf26d75 100644 --- a/hibernation/reconciler_test.go +++ b/hibernation/reconciler_test.go @@ -129,7 +129,7 @@ func TestReconcileAddsFinalizerAndRequeues(t *testing.T) { WithObjects(hib). WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} res, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, @@ -171,7 +171,7 @@ func TestReconcileDeleteClearsHibernateTagAndFinalizer(t *testing.T) { WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() reg := &fakeRegistry{} - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: reg} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: reg} if _, err := r.reconcileDelete(t.Context(), hib); err != nil { t.Fatalf("reconcileDelete: %v", err) @@ -207,7 +207,7 @@ func TestReconcileDeleteSkipsTagWhenVMNameMissing(t *testing.T) { WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() reg := &fakeRegistry{} - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: reg} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: reg} if _, err := r.reconcileDelete(t.Context(), hib); err != nil { t.Fatalf("reconcileDelete: %v", err) @@ -248,9 +248,9 @@ func TestReconcileHibernateSurfacesProbeError(t *testing.T) { Build() r := &Reconciler{ - Client: cli, - Scheme: scheme, - Epoch: &fakeRegistry{manifestErr: errors.New("transport boom")}, + Client: cli, + Scheme: scheme, + Registry: &fakeRegistry{manifestErr: errors.New("transport boom")}, } res, err := r.Reconcile(t.Context(), ctrl.Request{ @@ -259,7 +259,7 @@ func TestReconcileHibernateSurfacesProbeError(t *testing.T) { if err == nil { t.Fatalf("expected probe error to surface from Reconcile, got nil") } - if !errors.Is(err, r.Epoch.(*fakeRegistry).manifestErr) { + if !errors.Is(err, r.Registry.(*fakeRegistry).manifestErr) { t.Errorf("Reconcile err = %v, want wrap of transport boom", err) } if res.RequeueAfter != 0 { @@ -287,9 +287,9 @@ func TestReconcileHibernateFoldsAbsenceToRequeue(t *testing.T) { Build() r := &Reconciler{ - Client: cli, - Scheme: scheme, - Epoch: &fakeRegistry{}, // absent, no error + Client: cli, + Scheme: scheme, + Registry: &fakeRegistry{}, // absent, no error } res, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, @@ -381,7 +381,7 @@ func TestReconcileWakeFailsOnTimeout(t *testing.T) { WithObjects(hib, pod). WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} if _, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, @@ -427,7 +427,7 @@ func TestReconcileWakeRecoversFromFailed(t *testing.T) { WithObjects(hib, pod). WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} if _, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, @@ -490,7 +490,7 @@ func TestReconcileWakeClearsHibernateResidueOnFastPath(t *testing.T) { WithObjects(hib, pod). WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} if _, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, @@ -585,7 +585,7 @@ func TestReconcileHibernateFailsOnTimeout(t *testing.T) { WithObjects(hib, pod). WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} if _, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, @@ -631,7 +631,7 @@ func TestReconcileHibernateRecoversFromFailed(t *testing.T) { WithObjects(hib, pod). WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} if _, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, @@ -734,7 +734,7 @@ func TestReconcilePendingWhenPodMissing(t *testing.T) { WithObjects(hib). WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} res, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, @@ -771,7 +771,7 @@ func TestReconcilePendingWhenPodMissingVMName(t *testing.T) { WithObjects(hib, pod). WithStatusSubresource(&cocoonv1.CocoonHibernation{}). Build() - r := &Reconciler{Client: cli, Scheme: scheme, Epoch: &fakeRegistry{}} + r := &Reconciler{Client: cli, Scheme: scheme, Registry: &fakeRegistry{}} res, err := r.Reconcile(t.Context(), ctrl.Request{ NamespacedName: types.NamespacedName{Namespace: "ns", Name: "hib"}, diff --git a/hibernation/wake.go b/hibernation/wake.go index 4df03a6..8389297 100644 --- a/hibernation/wake.go +++ b/hibernation/wake.go @@ -25,7 +25,7 @@ func (r *Reconciler) reconcileWake(ctx context.Context, hib *cocoonv1.CocoonHibe if vmClonedAndRunning(pod) { // Drop snapshot tag (non-fatal; stale tag gets overwritten on next hibernate). - if err := r.Epoch.DeleteManifest(ctx, vmName, meta.HibernateSnapshotTag); err != nil { + if err := r.Registry.DeleteManifest(ctx, vmName, meta.HibernateSnapshotTag); err != nil { logger.Warnf(ctx, "delete hibernation snapshot %s: %v", vmName, err) } if r.firstTransitionAt(hib) { diff --git a/main.go b/main.go index 8a51466..69e5c11 100644 --- a/main.go +++ b/main.go @@ -110,7 +110,7 @@ func main() { if err = (&cocoonset.Reconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Epoch: registry, + Registry: registry, Recorder: recorder, }).SetupWithManager(ctx, mgr); err != nil { logger.Fatalf(ctx, err, "register cocoonset.Reconciler: %v", err) @@ -118,7 +118,7 @@ func main() { if err = (&hibernation.Reconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Epoch: registry, + Registry: registry, Recorder: recorder, }).SetupWithManager(ctx, mgr); err != nil { logger.Fatalf(ctx, err, "register hibernation.Reconciler: %v", err) From e26acef2fa35e64a36cd243d0c57a1c5cd63c8fc Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 1 Jul 2026 14:39:26 +0800 Subject: [PATCH 3/4] feat: drop the epoch backend, go OCI-only buildRegistry now requires OCI_REGISTRY and always builds a cocoon-common oci client (GCP ADC then docker config); the registryclient fallback and EPOCH_URL/TOKEN are gone, and epoch is removed from go.mod. Neutralizes epoch references in comments, an event, and the README. Bumps cocoon-common to the HasBlob/ImportImage rename. --- README.md | 21 +++++++++------------ cocoonset/delete.go | 4 ++-- cocoonset/suspend.go | 6 +++--- go.mod | 3 +-- go.sum | 6 ++---- hibernation/hibernate.go | 2 +- hibernation/reconciler.go | 2 +- main.go | 19 ++++++++----------- main_test.go | 17 +++++------------ snapshot/registry.go | 3 +-- 10 files changed, 33 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 0a9a616..1347988 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ cocoon-operator/ │ ┌────────────────────────┐ ┌─────────────────────────────┐ │ │ │ cocoonset.Reconciler │ │ hibernation.Reconciler │ │ │ │ - finalizer + GC │ │ - HibernateState patches │ │ -│ │ - main → subs → tbs │ │ - epoch.HasManifest probe │ │ +│ │ - main → subs → tbs │ │ - registry manifest probe │ │ │ │ - patch /status │ │ - Conditions │ │ │ └────────┬───────────────┘ └────────────┬────────────────┘ │ │ │ │ │ @@ -44,12 +44,12 @@ cocoon-operator/ ### CocoonSet reconcile loop 1. Fetch the CocoonSet (return early on NotFound). -2. If `DeletionTimestamp` is set, walk owned pods, delete them, `epoch.DeleteManifest` for both `:latest` and `:hibernate` tags on every owned VM (unconditional — DeleteManifest is 404-tolerant, and hibernate pushes ignore snapshotPolicy so any main-only gate would orphan `:hibernate` tags pushed by sub-agents), then drop the finalizer. VM names to GC are stashed onto an annotation before pod deletion so the cleanup survives a CocoonSet deleted before `Status.Agents` was ever patched. +2. If `DeletionTimestamp` is set, walk owned pods, delete them, `Registry.DeleteManifest` for both `:latest` and `:hibernate` tags on every owned VM (unconditional — DeleteManifest is 404-tolerant, and hibernate pushes ignore snapshotPolicy so any main-only gate would orphan `:hibernate` tags pushed by sub-agents), then drop the finalizer. VM names to GC are stashed onto an annotation before pod deletion so the cleanup survives a CocoonSet deleted before `Status.Agents` was ever patched. 3. Ensure the `cocoonset.cocoonstack.io/finalizer` is in place. 4. List owned pods by `cocoonset.cocoonstack.io/name=`, drop any with stale labels that aren't actually controller-owned, and classify the rest by role label. 5. **Lifecycle-bridge stamp**: patch `cs.Generation` onto each owned pod's `cocoonset.cocoonstack.io/generation` annotation so vk-cocoon can echo it back as `lifecycle-observed-generation`, giving clients a counter-based completion signal immune to wallclock skew. 6. **Failed-state short-circuit**: if the main pod is terminal (`Pod.Phase=Failed`, or it carries `vm.cocoonstack.io/lifecycle-state=Failed` from vk-cocoon while still Running), patch `Phase=Failed` and emit `MainAgentFailed` / `PodLifecycleFailed`. The Failed phase is recoverable: when the main pod becomes `Ready` again the operator emits `RecoveredFromFailure` and resumes normal reconciliation. -7. **Suspend short-circuit**: if `spec.suspend == true`, write `meta.HibernateState(true)` onto every owned pod and poll epoch for `:hibernate` manifests on every managed VM. Stay in `Phase=Suspending` (requeueing every 5 s) until every required snapshot lands, then transition to `Phase=Suspended`. +7. **Suspend short-circuit**: if `spec.suspend == true`, write `meta.HibernateState(true)` onto every owned pod and poll the registry for `:hibernate` manifests on every managed VM. Stay in `Phase=Suspending` (requeueing every 5 s) until every required snapshot lands, then transition to `Phase=Suspended`. 8. **Un-suspend**: if `spec.suspend == false` and any owned pod still carries the hibernate annotation from a prior suspend, clear it via `PatchHibernateState(false)` so vk-cocoon wakes the VMs. Pods that are the active target of a `desire=Hibernate` CocoonHibernation CR are skipped to avoid racing the hibernation reconciler. `PatchHibernateState(false)` is a no-op on pods whose annotation is already absent, so this is cheap in the common "never suspended" case. 9. Ensure the **main agent** (slot 0). If the existing pod has drifted from spec, delete it for recreate. If it is not yet `Ready`, requeue in 5 s and report `Phase=Pending`. 10. Ensure sub-agents `[1..Replicas]` (creates are fanned out via an errgroup capped at 8 concurrent pod creates so a large scale-up does not burst the apiserver); delete extras above the requested count. @@ -62,12 +62,12 @@ Pods are constructed via `meta.FromAgentSpec` / `meta.FromToolboxSpec` factory h | Spec.Desire | What the reconciler does | Terminal phase | |---|---|---| -| `Hibernate` | `meta.HibernateState(true).Apply` on the target pod, then poll `epoch.HasManifest(vmName, meta.HibernateSnapshotTag)` until the snapshot lands or `hibernateTimeout` (3 minutes) trips. A probe error (transport / 5xx / auth) surfaces as a returned error so controller-runtime logs + retries with backoff. | `Hibernated` | -| `Wake` | Check if the container is already `Running` (skip annotation patch if so), otherwise clear `meta.HibernateState` **once** (skip if already cleared to avoid triggering informer events on every requeue cycle), then wait for the container to be `Running` and drop the hibernation snapshot tag from epoch. A wake that does not complete within `wakeTimeout` (5 minutes) is escalated to `Phase=Failed` with a dated message in the `Ready` condition. | `Active` | +| `Hibernate` | `meta.HibernateState(true).Apply` on the target pod, then poll `Registry.HasManifest(vmName, meta.HibernateSnapshotTag)` until the snapshot lands or `hibernateTimeout` (3 minutes) trips. A probe error (transport / 5xx / auth) surfaces as a returned error so controller-runtime logs + retries with backoff. | `Hibernated` | +| `Wake` | Check if the container is already `Running` (skip annotation patch if so), otherwise clear `meta.HibernateState` **once** (skip if already cleared to avoid triggering informer events on every requeue cycle), then wait for the container to be `Running` and drop the hibernation snapshot tag from the registry. A wake that does not complete within `wakeTimeout` (5 minutes) is escalated to `Phase=Failed` with a dated message in the `Ready` condition. | `Active` | -On CR deletion the reconciler runs a finalizer (`cocoonhibernation.cocoonset.cocoonstack.io/finalizer`) that clears the `:hibernate` tag from epoch (if `Status.VMName` is set) before removing itself, so deleting a CocoonHibernation never leaves an orphaned snapshot on the registry. +On CR deletion the reconciler runs a finalizer (`cocoonhibernation.cocoonset.cocoonstack.io/finalizer`) that clears the `:hibernate` tag from the registry (if `Status.VMName` is set) before removing itself, so deleting a CocoonHibernation never leaves an orphaned snapshot on the registry. -There is no `cocoon-vm-snapshots` ConfigMap bridge — epoch is the single source of truth for hibernation state. Failure paths set `Phase=Failed` with a one-shot message in the `Ready` condition instead of looping forever on a bad reference. Both `Hibernate` and `Wake` Failed phases are recoverable: on re-entry from a non-deadline phase the reconciler refreshes the Ready condition's `LastTransitionTime` so the budget resets cleanly (without the override, `apimeta.SetStatusCondition` would preserve the stale timestamp across the `False → False` transition and the recovered phase would trip the deadline on the next reconcile). Each retry emits a `RetryRequested` Normal Event so the recovery is visible in `kubectl describe`. +There is no `cocoon-vm-snapshots` ConfigMap bridge — the registry is the single source of truth for hibernation state. Failure paths set `Phase=Failed` with a one-shot message in the `Ready` condition instead of looping forever on a bad reference. Both `Hibernate` and `Wake` Failed phases are recoverable: on re-entry from a non-deadline phase the reconciler refreshes the Ready condition's `LastTransitionTime` so the budget resets cleanly (without the override, `apimeta.SetStatusCondition` would preserve the stale timestamp across the `False → False` transition and the recovered phase would trip the deadline on the next reconcile). Each retry emits a `RetryRequested` Normal Event so the recovery is visible in `kubectl describe`. ### Observability @@ -101,9 +101,7 @@ cocoon_operator_lifecycle_state_failed_observed_total{phase} |---|---|---| | `KUBECONFIG` | unset | Path to kubeconfig when running outside the cluster | | `OPERATOR_LOG_LEVEL` | `info` | `projecteru2/core/log` level | -| `EPOCH_URL` | `http://epoch.cocoon-system.svc:8080` | Base URL of the epoch registry | -| `EPOCH_TOKEN` | unset | Bearer token (read-only is enough) | -| `EPOCH_CA_CERT` | unset | Read internally by `github.com/cocoonstack/epoch/registryclient` — see that package for the exact env names. | +| `OCI_REGISTRY` | **required** | OCI registry base for snapshot manifests (e.g. an Artifact Registry repo). Auth resolves GCP ADC then docker config. | | `METRICS_ADDR` | `:8080` | Prometheus listener | | `PROBE_ADDR` | `:8081` | healthz / readyz listener | | `LEADER_ELECT` | `true` | Enable leader election so only one replica reconciles | @@ -153,9 +151,8 @@ The Makefile detects Go workspace mode (`go env GOWORK`) and skips `go mod tidy` | Project | Role | |---|---| -| [cocoon-common](https://github.com/cocoonstack/cocoon-common) | CRD types, annotation contract, shared helpers | +| [cocoon-common](https://github.com/cocoonstack/cocoon-common) | CRD types, annotation contract, shared helpers, and the OCI registry client | | [cocoon-webhook](https://github.com/cocoonstack/cocoon-webhook) | Admission webhook for sticky scheduling and CocoonSet validation | -| [epoch](https://github.com/cocoonstack/epoch) | Snapshot registry; the operator queries it via the `snapshot.Registry` interface | | [vk-cocoon](https://github.com/cocoonstack/vk-cocoon) | Virtual kubelet provider managing VM lifecycle | ## License diff --git a/cocoonset/delete.go b/cocoonset/delete.go index ade6e32..7e12849 100644 --- a/cocoonset/delete.go +++ b/cocoonset/delete.go @@ -47,7 +47,7 @@ func (r *Reconciler) reconcileDelete(ctx context.Context, cs *cocoonv1.CocoonSet } // Requeue if any pods still exist — vk-cocoon's DeletePod may still be running - // snapshot save/push. We only GC epoch tags once every pod is fully gone. + // snapshot save/push. We only GC registry tags once every pod is fully gone. remainingOwned, listErr := r.listOwnedPods(ctx, cs) if listErr != nil { return ctrl.Result{}, fmt.Errorf("re-list pods after delete: %w", listErr) @@ -72,7 +72,7 @@ func (r *Reconciler) reconcileDelete(ctx context.Context, cs *cocoonv1.CocoonSet } } } else { - logger.Warnf(ctx, "skipping epoch tag GC for cocoonset %s/%s: registry not configured", cs.Namespace, cs.Name) + logger.Warnf(ctx, "skipping registry tag GC for cocoonset %s/%s: registry not configured", cs.Namespace, cs.Name) } if controllerutil.ContainsFinalizer(cs, finalizerName) { diff --git a/cocoonset/suspend.go b/cocoonset/suspend.go index 3097679..a0712ec 100644 --- a/cocoonset/suspend.go +++ b/cocoonset/suspend.go @@ -18,7 +18,7 @@ import ( ) // reconcileSuspend ensures the main agent exists, applies the hibernate -// annotation to every owned pod, then polls epoch to observe when all +// annotation to every owned pod, then polls the registry to observe when all // managed VMs have been pushed to snapshot. Stays in Suspending with a // periodic requeue until every required snapshot lands. func (r *Reconciler) reconcileSuspend(ctx context.Context, cs *cocoonv1.CocoonSet, classified classifiedPods) (ctrl.Result, error) { @@ -51,13 +51,13 @@ func (r *Reconciler) reconcileSuspend(ctx context.Context, cs *cocoonv1.CocoonSe } // allOwnedPodsHibernated reports whether every managed owned pod has a -// hibernate snapshot published to epoch. Unmanaged pods (e.g. static +// hibernate snapshot published to the registry. Unmanaged pods (e.g. static // toolboxes) are skipped since they have no VM lifecycle to observe. // Returns (false, nil) whenever the expected state is not yet observed so // the caller requeues rather than treats it as an error. func (r *Reconciler) allOwnedPodsHibernated(ctx context.Context, classified classifiedPods) (bool, error) { if r.Registry == nil { - // No registry configured; epoch-less deployments have no snapshot to + // No registry configured; such deployments have no snapshot to // observe, so treat the annotation write as authoritative. return true, nil } diff --git a/go.mod b/go.mod index 33a933e..c281f19 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,7 @@ module github.com/cocoonstack/cocoon-operator go 1.25.6 require ( - github.com/cocoonstack/cocoon-common v0.2.3-0.20260701050723-6e675f810e8d - github.com/cocoonstack/epoch v0.2.4 + github.com/cocoonstack/cocoon-common v0.2.3-0.20260701062002-fd96a6d3865b github.com/go-logr/logr v1.4.3 github.com/google/go-containerregistry v0.21.7 github.com/projecteru2/core v0.0.0-20241016125006-ff909eefe04c diff --git a/go.sum b/go.sum index 960f749..cfc16ed 100644 --- a/go.sum +++ b/go.sum @@ -29,10 +29,8 @@ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZe github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cocoonstack/cocoon-common v0.2.3-0.20260701050723-6e675f810e8d h1:8IAri8O+Sy6r+JDONULPgXx6YULohMFh05108uJLbpg= -github.com/cocoonstack/cocoon-common v0.2.3-0.20260701050723-6e675f810e8d/go.mod h1:/Cf3aBBN0blBxJWexuGuMbTkas+scvQiF2I75aQXkH4= -github.com/cocoonstack/epoch v0.2.4 h1:X18DdsWlPRqEVswR1N3JAazxRZfBUtoL5r7GTgWmhN8= -github.com/cocoonstack/epoch v0.2.4/go.mod h1:1bFeUom4aeFbw3vjFbrLegEgVShqe4qSOfpmH5UEpww= +github.com/cocoonstack/cocoon-common v0.2.3-0.20260701062002-fd96a6d3865b h1:c8hva3RL4dIpWugsvgD7rs/E2IPSE8gKrVy5nrbRlLg= +github.com/cocoonstack/cocoon-common v0.2.3-0.20260701062002-fd96a6d3865b/go.mod h1:/Cf3aBBN0blBxJWexuGuMbTkas+scvQiF2I75aQXkH4= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= diff --git a/hibernation/hibernate.go b/hibernation/hibernate.go index ad099eb..5fa7881 100644 --- a/hibernation/hibernate.go +++ b/hibernation/hibernate.go @@ -26,7 +26,7 @@ func (r *Reconciler) reconcileHibernate(ctx context.Context, hib *cocoonv1.Cocoo if present { if r.firstTransitionAt(hib) { observePhaseExit(hib, "ok") - r.emitNormalf(hib, "Hibernated", "snapshot %s pushed to epoch", vmName) + r.emitNormalf(hib, "Hibernated", "snapshot %s pushed to the registry", vmName) } return ctrl.Result{}, r.setPhase(ctx, hib, cocoonv1.CocoonHibernationPhaseHibernated, vmName) } diff --git a/hibernation/reconciler.go b/hibernation/reconciler.go index 48ff36a..6807bc6 100644 --- a/hibernation/reconciler.go +++ b/hibernation/reconciler.go @@ -41,7 +41,7 @@ const ( // pod watcher can resolve a pod event back to the CRs that target it. indexPodRefName = "spec.podRef.name" - // finalizerName keeps the CR alive long enough to clear its :hibernate tag from epoch. + // finalizerName keeps the CR alive long enough to clear its :hibernate tag from the registry. finalizerName = "cocoonhibernation.cocoonset.cocoonstack.io/finalizer" conditionReasonPending = "Pending" diff --git a/main.go b/main.go index 69e5c11..5593b41 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ package main import ( "context" + "errors" "flag" "fmt" "os" @@ -35,7 +36,6 @@ import ( "github.com/cocoonstack/cocoon-operator/metrics" "github.com/cocoonstack/cocoon-operator/snapshot" "github.com/cocoonstack/cocoon-operator/version" - "github.com/cocoonstack/epoch/registryclient" ) const ( @@ -134,18 +134,15 @@ func main() { } } -// buildRegistry selects the registry backend: an OCI registry when OCI_REGISTRY -// is set (GCP ADC then docker config), else the epoch backend. +// buildRegistry builds the OCI registry backend from OCI_REGISTRY. The keychain +// resolves GCP ADC (google.Keychain) then docker config. func buildRegistry() (snapshot.Registry, error) { - if base := os.Getenv("OCI_REGISTRY"); base != "" { - keychain := authn.NewMultiKeychain(google.Keychain, authn.DefaultKeychain) - return oci.NewOCIRegistry(base, keychain), nil + base := os.Getenv("OCI_REGISTRY") + if base == "" { + return nil, errors.New("OCI_REGISTRY must be set") } - client, err := registryclient.NewFromEnv(commonk8s.EnvOrDefault("EPOCH_URL", "http://epoch.cocoon-system.svc:8080"), os.Getenv("EPOCH_TOKEN")) - if err != nil { - return nil, err - } - return client, nil + keychain := authn.NewMultiKeychain(google.Keychain, authn.DefaultKeychain) + return oci.NewOCIRegistry(base, keychain), nil } func buildScheme() *runtime.Scheme { diff --git a/main_test.go b/main_test.go index 7417594..6c82f8a 100644 --- a/main_test.go +++ b/main_test.go @@ -4,27 +4,20 @@ import ( "testing" "github.com/cocoonstack/cocoon-common/oci" - "github.com/cocoonstack/epoch/registryclient" ) -func TestBuildRegistryBackend(t *testing.T) { - t.Setenv("EPOCH_CA_CERT", "") // keep the epoch client deterministic on dev machines - +func TestBuildRegistry(t *testing.T) { t.Setenv("OCI_REGISTRY", "example.com/proj/repo") reg, err := buildRegistry() if err != nil { - t.Fatalf("buildRegistry(OCI): %v", err) + t.Fatalf("buildRegistry: %v", err) } if _, ok := reg.(*oci.OCIRegistry); !ok { - t.Fatalf("OCI_REGISTRY set: got %T, want *oci.OCIRegistry", reg) + t.Fatalf("got %T, want *oci.OCIRegistry", reg) } t.Setenv("OCI_REGISTRY", "") - ep, err := buildRegistry() - if err != nil { - t.Fatalf("buildRegistry(epoch): %v", err) - } - if _, ok := ep.(*registryclient.Client); !ok { - t.Fatalf("no OCI_REGISTRY: got %T, want *registryclient.Client", ep) + if _, err := buildRegistry(); err == nil { + t.Fatal("buildRegistry with no OCI_REGISTRY: want error, got nil") } } diff --git a/snapshot/registry.go b/snapshot/registry.go index b2929f2..f328392 100644 --- a/snapshot/registry.go +++ b/snapshot/registry.go @@ -5,8 +5,7 @@ package snapshot import "context" // Registry is the subset of registry operations the operator reconcilers need. -// Both cocoon-common's oci.OCIRegistry and epoch's registryclient.Client -// satisfy it; tests swap in fakes. +// cocoon-common's oci.OCIRegistry satisfies it; tests swap in fakes. type Registry interface { HasManifest(ctx context.Context, name, reference string) (bool, error) DeleteManifest(ctx context.Context, name, reference string) error From 8ae9b35d54e1da29270e97e389be529094ccdace Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 1 Jul 2026 14:50:23 +0800 Subject: [PATCH 4/4] build: pin cocoon-common to the merged main commit --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c281f19..46e40dd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/cocoonstack/cocoon-operator go 1.25.6 require ( - github.com/cocoonstack/cocoon-common v0.2.3-0.20260701062002-fd96a6d3865b + github.com/cocoonstack/cocoon-common v0.2.3-0.20260701064759-3dcdfdd23a16 github.com/go-logr/logr v1.4.3 github.com/google/go-containerregistry v0.21.7 github.com/projecteru2/core v0.0.0-20241016125006-ff909eefe04c diff --git a/go.sum b/go.sum index cfc16ed..ec07633 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZe github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cocoonstack/cocoon-common v0.2.3-0.20260701062002-fd96a6d3865b h1:c8hva3RL4dIpWugsvgD7rs/E2IPSE8gKrVy5nrbRlLg= -github.com/cocoonstack/cocoon-common v0.2.3-0.20260701062002-fd96a6d3865b/go.mod h1:/Cf3aBBN0blBxJWexuGuMbTkas+scvQiF2I75aQXkH4= +github.com/cocoonstack/cocoon-common v0.2.3-0.20260701064759-3dcdfdd23a16 h1:4gS1Pr0nCN/qoa344ZHqy2vZPaCW8JcCocB7mgqJmUk= +github.com/cocoonstack/cocoon-common v0.2.3-0.20260701064759-3dcdfdd23a16/go.mod h1:/Cf3aBBN0blBxJWexuGuMbTkas+scvQiF2I75aQXkH4= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=