diff --git a/.github/workflows/dev_module_build.yml b/.github/workflows/dev_module_build.yml index 373bafcb8c..01b67b2174 100644 --- a/.github/workflows/dev_module_build.yml +++ b/.github/workflows/dev_module_build.yml @@ -290,10 +290,14 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Run test hooks + - name: Run test Python hooks run: | task hooks:test + - name: Run test GO hooks + run: | + task gohooks:test + - name: Run unit test virtualization-controller run: | task virtualization-controller:init diff --git a/Taskfile.yaml b/Taskfile.yaml index 38fb0a446d..c022128cf7 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -15,6 +15,9 @@ includes: hooks: taskfile: ./hooks dir: ./hooks + gohooks: + taskfile: ./images/hooks + dir: ./images/hooks e2e: taskfile: ./tests/e2e dir: ./tests/e2e diff --git a/base-images/virtualization_images.yml b/base-images/virtualization_images.yml index 4d8702160f..82d9aa5932 100644 --- a/base-images/virtualization_images.yml +++ b/base-images/virtualization_images.yml @@ -6,6 +6,7 @@ BASE_DEBIAN_BOOKWORM_SLIM: "debian:bookworm-slim@sha256:e9ac68ffde903b241342267a BASE_CONTAINER_REGISTRY: "registry:2.8.3@sha256:ac0192b549007e22998eb74e8d8488dcfe70f1489520c3b144a6047ac5efbe90" BASE_GOLANG_22_BOOKWORM: "golang:1.22.8-bookworm@sha256:9e7db50b9858e9cd804043200f1e6acd5a11111151ce886951c9fe3523002cea" BASE_GOLANG_23_BOOKWORM: "golang:1.23.6-bookworm@sha256:441f59f8a2104b99320e1f5aaf59a81baabbc36c81f4e792d5715ef09dd29355" +BASE_GOLANG_24_BOOKWORM: "golang:1.24.2-bookworm@sha256:79390b5e5af9ee6e7b1173ee3eac7fadf6751a545297672916b59bfa0ecf6f71" BASE_ALT_P10: "alt:p10@sha256:4fab03b8d23eb16147397b0bc41a5025ba59f4e834f7fb4b933ac5206431d740" # Digest for image created at 2024-09-20. diff --git a/hooks/discovery_clusterip_service_for_dvcr.py b/hooks/discovery_clusterip_service_for_dvcr.py deleted file mode 100755 index 58c0492a92..0000000000 --- a/hooks/discovery_clusterip_service_for_dvcr.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2023 Flant JSC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Callable -from deckhouse import hook -from lib.hooks.hook import Hook -import common - - -class DiscoveryClusterIPServiceHook(Hook): - SNAPSHOT_NAME = "discovery-service" - - def __init__(self, module_name: str): - self.module_name = module_name - self.namespace = common.NAMESPACE - self.service_name = "dvcr" - self.value_path = f"{self.module_name}.internal.dvcr.serviceIP" - - def generate_config(self) -> dict: - return { - "configVersion": "v1", - "beforeHelm": 5, - "kubernetes": [ - { - "name": self.SNAPSHOT_NAME, - "apiVersion": "v1", - "kind": "Service", - "nameSelector": { - "matchNames": [self.service_name] - }, - "namespace": { - "nameSelector": { - "matchNames": [self.namespace] - } - }, - "includeSnapshotsFrom": [self.SNAPSHOT_NAME], - "jqFilter": '{"clusterIP": .spec.clusterIP}', - "queue": f"/modules/{self.module_name}/discovery-service", - "keepFullObjectsInMemory": False - }, - ] - } - - def reconcile(self) -> Callable[[hook.Context], None]: - def r(ctx: hook.Context): - services = [s["filterResult"]["clusterIP"] - for s in ctx.snapshots.get(self.SNAPSHOT_NAME, [])] - if len(services) == 0: - print( - f"Service dvcr not found. Delete value from {self.value_path}.") - self.delete_value(self.value_path, ctx.values) - else: - print(f"Set {services[0]} to {self.value_path}.") - self.set_value(self.value_path, ctx.values, services[0]) - return r - - -if __name__ == "__main__": - h = DiscoveryClusterIPServiceHook(common.MODULE_NAME) - h.run() diff --git a/hooks/generate_secret_for_dvcr.py b/hooks/generate_secret_for_dvcr.py deleted file mode 100755 index c3cf875cd4..0000000000 --- a/hooks/generate_secret_for_dvcr.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2023 Flant JSC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Callable -from deckhouse.hook import Context -from lib.hooks.hook import Hook -from lib.password_generator import password_generator, htpasswd as hd -import common -import lib.utils as utils - - -class KeyHtpasswd: - def __init__(self, - name: str, - username: str, - value_path: str): - self.name = name - self.username = username - self.value_path = value_path - - -class Key: - def __init__(self, - name: str, - value_path: str, - length: int = 64, - htpasswd: KeyHtpasswd = None): - self.name = name - self.value_path = value_path - self.length = length - self.htpasswd = htpasswd - - -class GenerateSecretHook(Hook): - SNAPSHOT_NAME = "secrets" - - def __init__(self, - *keys: Key, - secret_name: str, - namespace: str, - module_name: str, - ): - self.module_name = module_name - self.keys = keys - self.secret_name = secret_name - self.namespace = namespace - self.queue = f"/modules/{self.module_name}/generate_secrets" - - def generate_config(self) -> dict: - return { - "configVersion": "v1", - "beforeHelm": 5, - "kubernetes": [ - { - "name": self.SNAPSHOT_NAME, - "apiVersion": "v1", - "kind": "Secret", - "nameSelector": { - "matchNames": [self.secret_name] - }, - "namespace": { - "nameSelector": { - "matchNames": [self.namespace] - } - }, - "includeSnapshotsFrom": [self.SNAPSHOT_NAME], - "jqFilter": '{"data": .data}', - "queue": self.queue, - "keepFullObjectsInMemory": False - }, - ] - } - - def reconcile(self) -> Callable[[Context], None]: - def r(ctx: Context): - for key in self.keys: - try: - key_base64 = ctx.snapshots[self.SNAPSHOT_NAME][0]["filterResult"]["data"][key.name] - self.set_value(key.value_path, ctx.values, key_base64) - except (IndexError, KeyError): - print( - f"Generate new key {key.name} for secret={self.secret_name}.") - genkey = utils.base64_encode_from_str( - password_generator.alpha_num(key.length)) - self.set_value(key.value_path, ctx.values, genkey) - for key in self.keys: - if key.htpasswd is None: - continue - password = utils.base64_decode( - self.get_value(key.value_path, ctx.values)) - htpasswd = hd.Htpasswd( - username=key.htpasswd.username, password=password) - regenerate = False - try: - htpasswd_base64 = ctx.snapshots[self.SNAPSHOT_NAME][0]["filterResult"]["data"][key.htpasswd.name] - if htpasswd.validate(utils.base64_decode(htpasswd_base64)): - self.set_value(key.htpasswd.value_path, - ctx.values, htpasswd_base64) - continue - regenerate = True - except (IndexError, KeyError): - regenerate = True - if regenerate: - print( - f"Generate new htpasswd for key={key.name} and secret={self.secret_name}.") - encoded_htpasswd = utils.base64_encode_from_str( - htpasswd.generate()) - self.set_value(key.htpasswd.value_path, - ctx.values, encoded_htpasswd) - return r - - -if __name__ == "__main__": - hook = GenerateSecretHook( - Key(name="passwordRW", - value_path=f"{common.MODULE_NAME}.internal.dvcr.passwordRW", - length=32, - htpasswd=KeyHtpasswd( - name="htpasswd", - username="admin", - value_path=f"{common.MODULE_NAME}.internal.dvcr.htpasswd", - ) - ), - Key(name="salt", - value_path=f"{common.MODULE_NAME}.internal.dvcr.salt"), - secret_name="dvcr-secrets", - namespace=common.NAMESPACE, - module_name=common.MODULE_NAME - ) - hook.run() diff --git a/hooks/test_discovery_clusterip_service_for_dvcr.py b/hooks/test_discovery_clusterip_service_for_dvcr.py deleted file mode 100644 index 978cca7027..0000000000 --- a/hooks/test_discovery_clusterip_service_for_dvcr.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2023 Flant JSC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from discovery_clusterip_service_for_dvcr import DiscoveryClusterIPServiceHook -from lib.tests import testing - -CLUSTER_IP_ADD = "10.10.10.10" -CLUSTER_IP_CHANGE = "11.11.11.11" - -hook = DiscoveryClusterIPServiceHook(module_name="test") - -binding_context_add = [ - { - "binding": hook.SNAPSHOT_NAME, - "snapshots": { - hook.SNAPSHOT_NAME: [ - { - "filterResult": { - "clusterIP": CLUSTER_IP_ADD - } - } - ] - } - } -] - -initial_values_add = { - "test": { - "internal": {} - } -} - -binding_context_delete = [ - { - "binding": hook.SNAPSHOT_NAME, - "snapshots": { - hook.SNAPSHOT_NAME: [] - } - } -] - -binding_context_change = [ - { - "binding": hook.SNAPSHOT_NAME, - "snapshots": { - hook.SNAPSHOT_NAME: [ - { - "filterResult": { - "clusterIP": CLUSTER_IP_CHANGE - } - } - ] - } - } -] - -initial_values_delete_or_change = { - "test": { - "internal": { - "dvcr": { - "serviceIP": CLUSTER_IP_ADD - } - } - } -} - - -class TestClusterIPAdd(testing.TestHook): - def setUp(self): - self.func = hook.reconcile() - self.bindind_context = binding_context_add - self.values = initial_values_add - - def test_adding(self): - self.hook_run() - self.assertEqual(self.values["test"]["internal"]["dvcr"]["serviceIP"], - CLUSTER_IP_ADD) - - -class TestClusterIPChange(testing.TestHook): - def setUp(self): - self.func = hook.reconcile() - self.bindind_context = binding_context_change - self.values = initial_values_delete_or_change - - def test_changing(self): - self.hook_run() - self.assertEqual(self.values["test"]["internal"]["dvcr"]["serviceIP"], - CLUSTER_IP_CHANGE) - - -class TestClusterIPDelete(testing.TestHook): - def setUp(self): - self.func = hook.reconcile() - self.bindind_context = binding_context_delete - self.values = initial_values_delete_or_change - - def test_deleting(self): - self.hook_run() - self.assertEqual(self.values["test"]["internal"]["dvcr"].get("serviceIP"), - None) diff --git a/hooks/test_generate_secret_for_dvcr.py b/hooks/test_generate_secret_for_dvcr.py deleted file mode 100644 index f05edbed43..0000000000 --- a/hooks/test_generate_secret_for_dvcr.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2023 Flant JSC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from lib.tests import testing -from lib.password_generator import htpasswd as hd -import lib.utils as utils -from generate_secret_for_dvcr import GenerateSecretHook, Key, KeyHtpasswd - -MODULE_NAME = "test" - -hook = GenerateSecretHook( - Key(name="key1", - value_path=f"{MODULE_NAME}.internal.test.key1", - length=32, - htpasswd=KeyHtpasswd( - name="htpasswd", - username="admin", - value_path=f"{MODULE_NAME}.internal.test.htpasswd", - ) - ), - Key(name="key2", - value_path=f"{MODULE_NAME}.internal.test.key2"), - secret_name=MODULE_NAME, - namespace=MODULE_NAME, - module_name=MODULE_NAME -) - -binding_context_generate_all = [ - { - "binding": hook.SNAPSHOT_NAME, - "snapshots": { - hook.SNAPSHOT_NAME: [] - } - } -] - - -initial_values_generate_all = { - MODULE_NAME: { - "internal": {} - } -} - -binding_context_regenerate_key1 = [ - { - "binding": hook.SNAPSHOT_NAME, - "snapshots": { - hook.SNAPSHOT_NAME: [ - { - "filterResult": { - "data": { - hook.keys[1].name: "" - } - } - } - ] - } - } -] - -binding_context_regenerate_key2 = [ - { - "binding": hook.SNAPSHOT_NAME, - "snapshots": { - hook.SNAPSHOT_NAME: [ - { - "filterResult": { - "data": { - hook.keys[0].name: "" - } - } - } - ] - } - } -] - -initial_values_regenerate_something = { - MODULE_NAME: { - "internal": { - "test": { - hook.keys[0].name: "", - hook.keys[1].name: "" - } - } - } -} - - -class TestGenerateKeys(testing.TestHook): - def key_test(self, key: str, lenght: int): - self.assertGreater(len(key), 0) - self.assertTrue(utils.is_base64(key)) - self.assertEqual(len(utils.base64_decode(key)), lenght) - - def get_key(self, key: str) -> str: - return self.values[MODULE_NAME]["internal"]["test"].get(key, "") - - -class TestGenerateSecretALL(TestGenerateKeys): - def setUp(self): - self.func = hook.reconcile() - self.bindind_context = binding_context_generate_all - self.values = initial_values_generate_all - - def test_generate_all(self): - self.hook_run() - self.key_test(self.get_key(hook.keys[0].name), hook.keys[0].length) - self.key_test(self.get_key(hook.keys[1].name), hook.keys[1].length) - - -class TestGenerateSecretKey1(TestGenerateKeys): - def setUp(self): - self.func = hook.reconcile() - self.bindind_context = binding_context_regenerate_key1 - self.values = initial_values_regenerate_something - - def test_generate_key1(self): - self.hook_run() - self.key_test(self.get_key(hook.keys[0].name), hook.keys[0].length) - - def test_generate_htpasswd(self): - self.hook_run() - htpasswd_base64 = self.get_key(hook.keys[0].htpasswd.name) - self.assertGreater(len(htpasswd_base64), 0) - self.assertTrue(utils.is_base64(htpasswd_base64)) - password_base64 = self.get_key(hook.keys[0].name) - self.key_test(password_base64, hook.keys[0].length) - htpasswd = hd.Htpasswd( - hook.keys[0].htpasswd.username, utils.base64_decode(password_base64)) - self.assertTrue(htpasswd.validate( - utils.base64_decode(htpasswd_base64))) - - -class TestGenerateSecretKey2(TestGenerateKeys): - def setUp(self): - self.func = hook.reconcile() - self.bindind_context = binding_context_regenerate_key2 - self.values = initial_values_regenerate_something - - def test_generate_key2(self): - self.hook_run() - self.key_test(self.get_key(hook.keys[1].name), hook.keys[1].length) diff --git a/images/hooks/Taskfile.yaml b/images/hooks/Taskfile.yaml new file mode 100644 index 0000000000..0a850b4ef4 --- /dev/null +++ b/images/hooks/Taskfile.yaml @@ -0,0 +1,13 @@ +version: "3" + +tasks: + test: + desc: Run tests + cmds: + - | + go tool ginkgo -v \ + --race \ + {{ if .FOCUS -}} + --focus "{{ .FOCUS }}" \ + {{ end -}} + ./... diff --git a/images/hooks/cmd/000-ca-discovery/main.go b/images/hooks/cmd/000-ca-discovery/main.go index 33f54e3f42..2bcae28ba8 100644 --- a/images/hooks/cmd/000-ca-discovery/main.go +++ b/images/hooks/cmd/000-ca-discovery/main.go @@ -18,6 +18,7 @@ package main import ( "context" "fmt" + "hooks/pkg/common" "github.com/deckhouse/module-sdk/pkg" @@ -88,7 +89,7 @@ func handlerModuleCommonCA(_ context.Context, input *pkg.HookInput) error { } // CA secret is found, decode it and save to Values. - err := ca_secret[0].UnmarhalTo(&rootCA) + err := ca_secret[0].UnmarshalTo(&rootCA) if err != nil { return fmt.Errorf("unmarshalTo: %w", err) } diff --git a/images/hooks/cmd/005-prevent-default-vmclasses-deletion/main.go b/images/hooks/cmd/005-prevent-default-vmclasses-deletion/main.go index eb04a7fd67..5b13fbd803 100644 --- a/images/hooks/cmd/005-prevent-default-vmclasses-deletion/main.go +++ b/images/hooks/cmd/005-prevent-default-vmclasses-deletion/main.go @@ -18,10 +18,12 @@ package main import ( "context" "fmt" - "hooks/pkg/common" + "github.com/deckhouse/virtualization/api/core" "github.com/deckhouse/virtualization/api/core/v1alpha2" + "hooks/pkg/common" + "github.com/deckhouse/module-sdk/pkg" "github.com/deckhouse/module-sdk/pkg/app" "github.com/deckhouse/module-sdk/pkg/registry" @@ -33,10 +35,9 @@ const ( removePassthroughHookName = "Prevent default VirtualMachineClasses deletion" removePassthroughHookJQFilter = `.metadata` // see https://helm.sh/docs/howto/charts_tips_and_tricks/#tell-helm-not-to-uninstall-a-resource - helmResourcePolicyKey = "helm.sh/resource-policy" - helmResourcePolicyKeep = "keep" - apiVersion = core.GroupName + "/" + v1alpha2.Version - + helmResourcePolicyKey = "helm.sh/resource-policy" + helmResourcePolicyKeep = "keep" + apiVersion = core.GroupName + "/" + v1alpha2.Version ) var _ = registry.RegisterFunc(config, Reconcile) @@ -71,7 +72,7 @@ func Reconcile(_ context.Context, input *pkg.HookInput) error { for _, vmc := range vmcs { metadata := &metav1.ObjectMeta{} - if err := vmc.UnmarhalTo(metadata); err != nil { + if err := vmc.UnmarshalTo(metadata); err != nil { input.Logger.Error(fmt.Sprintf("Failed to unmarshal metadata VirtualMachineClasses %v", err)) } diff --git a/images/hooks/cmd/006-generate-secret-for-dvcr/main.go b/images/hooks/cmd/006-generate-secret-for-dvcr/main.go new file mode 100644 index 0000000000..8032388564 --- /dev/null +++ b/images/hooks/cmd/006-generate-secret-for-dvcr/main.go @@ -0,0 +1,186 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "encoding/base64" + "fmt" + "math/rand/v2" + "time" + + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/pkg/app" + "github.com/deckhouse/module-sdk/pkg/registry" + "golang.org/x/crypto/bcrypt" + + "hooks/pkg/common" +) + +const ( + dvcrSecrets = "dvcr-secrets" + passwordRWValuePath = "virtualization.internal.dvcr.passwordRW" + saltValuePath = "virtualization.internal.dvcr.salt" + htpasswdValuePath = "virtualization.internal.dvcr.htpasswd" +) + +type dvcrSecretData struct { + PasswordRW string `json:"passwordRW"` + Salt string `json:"salt"` + Htpasswd string `json:"htpasswd"` +} + +var _ = registry.RegisterFunc(configModuleCommonCA, handlerDVVCRSecrets) + +var configModuleCommonCA = &pkg.HookConfig{ + OnBeforeHelm: &pkg.OrderedConfig{Order: 5}, + Kubernetes: []pkg.KubernetesConfig{ + { + Name: dvcrSecrets, + APIVersion: "v1", + Kind: "Secret", + JqFilter: `{"data": .data}`, + + NameSelector: &pkg.NameSelector{ + MatchNames: []string{dvcrSecrets}, + }, + + NamespaceSelector: &pkg.NamespaceSelector{ + NameSelector: &pkg.NameSelector{ + MatchNames: []string{common.MODULE_NAMESPACE}, + }, + }, + }, + }, + + Queue: fmt.Sprintf("modules/%s", common.MODULE_NAME), +} + +func handlerDVVCRSecrets(_ context.Context, input *pkg.HookInput) error { + dataFromSecret, err := getDVCRSecretsFromSecrets(input) + if err != nil { + return err + } + dataFromValues := getDVCRSecretsFromValues(input) + + passwordRW, found := firstNotEmpty(dataFromValues.PasswordRW, dataFromSecret.PasswordRW) + if !found { + input.Logger.Info("Regenerate PasswordRW") + passwordRW = alphaNum(32) + } + if passwordRW != dataFromValues.PasswordRW { + input.Logger.Info("Set PasswordRW to value") + input.Values.Set(passwordRWValuePath, base64.StdEncoding.EncodeToString([]byte(passwordRW))) + } + + salt, found := firstNotEmpty(dataFromValues.Salt, dataFromSecret.Salt) + if !found { + input.Logger.Info("Regenerate Salt") + salt = alphaNum(32) + } + if salt != dataFromValues.Salt { + input.Logger.Info("Set Salt to value") + input.Values.Set(saltValuePath, base64.StdEncoding.EncodeToString([]byte(salt))) + } + + htpasswd, found := firstNotEmpty(dataFromValues.Htpasswd, dataFromSecret.Htpasswd) + if !found || !validateHtpasswd(passwordRW, htpasswd) { + input.Logger.Info("Regenerate Htpasswd") + htpasswd, err = generateHtpasswd(passwordRW) + if err != nil { + return fmt.Errorf("generate htpasswd: %w", err) + } + } + if htpasswd != dataFromValues.Htpasswd { + input.Logger.Info("Set Htpasswd to value") + input.Values.Set(htpasswdValuePath, base64.StdEncoding.EncodeToString([]byte(htpasswd))) + } + + return nil +} + +func getDVCRSecretsFromSecrets(input *pkg.HookInput) (dvcrSecretData, error) { + snapshots := input.Snapshots.Get(dvcrSecrets) + + dataFromSecret := dvcrSecretData{} + + if len(snapshots) > 0 { + err := snapshots[0].UnmarshalTo(&dataFromSecret) + if err != nil { + return dataFromSecret, fmt.Errorf("unmarshalTo: %w", err) + } + } + + return dataFromSecret, nil +} +func getDVCRSecretsFromValues(input *pkg.HookInput) dvcrSecretData { + passwordRW, err := base64.StdEncoding.DecodeString(input.Values.Get(passwordRWValuePath).String()) + if err != nil { + input.Logger.Error("Failed to decode passwordRW", err) + } + salt, err := base64.StdEncoding.DecodeString(input.Values.Get(saltValuePath).String()) + if err != nil { + input.Logger.Error("Failed to decode salt", err) + } + htpasswd, err := base64.StdEncoding.DecodeString(input.Values.Get(htpasswdValuePath).String()) + if err != nil { + input.Logger.Error("Failed to decode htpasswd", err) + } + return dvcrSecretData{ + PasswordRW: string(passwordRW), + Salt: string(salt), + Htpasswd: string(htpasswd), + } +} + +func firstNotEmpty(values ...string) (value string, found bool) { + for _, value := range values { + if value != "" { + return value, true + } + } + return "", false +} + +func generateHtpasswd(password string) (string, error) { + hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hashed), nil +} + +func validateHtpasswd(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +func alphaNum(length int) string { + rnd := rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), 0)) + + b := make([]byte, length) + for i := range b { + b[i] = letterBytes[rnd.IntN(len(letterBytes))] + } + return string(b) +} + +func main() { + app.Run() +} diff --git a/images/hooks/cmd/006-generate-secret-for-dvcr/main_test.go b/images/hooks/cmd/006-generate-secret-for-dvcr/main_test.go new file mode 100644 index 0000000000..4c497c2826 --- /dev/null +++ b/images/hooks/cmd/006-generate-secret-for-dvcr/main_test.go @@ -0,0 +1,229 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "encoding/base64" + "testing" + + "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/testing/mock" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/tidwall/gjson" +) + +func TestSetDVCRSecrets(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "DVCR Secrets Suite") +} + +var _ = Describe("DVCR Secrets", func() { + const ( + defaultPassword = "fJx5RfV6E2CMbjDCD7kQIktwQWGIXMY3" + defaultSalt = "nWe3Mof5rJQMsr605E47A3ZX9OHCWgVF" + defaultHtpasswd = "$2a$10$CLOPPEmQ/KaTclwdAzyXOuR.HDNvrQTtp2wRVWnAoAHbdx.Pq9CTi" + + defaultPasswordBase64 = "Zkp4NVJmVjZFMkNNYmpEQ0Q3a1FJa3R3UVdHSVhNWTM=" + defaultSaltBase64 = "bldlM01vZjVySlFNc3I2MDVFNDdBM1pYOU9IQ1dnVkY=" + defaultHtpasswdBase64 = "JDJhJDEwJENMT1BQRW1RL0thVGNsd2RBenlYT3VSLkhETnZyUVR0cDJ3UlZXbkFvQUhiZHguUHE5Q1Rp" + ) + var ( + dc *mock.DependencyContainerMock + snapshots *mock.SnapshotsMock + values *mock.PatchableValuesCollectorMock + ) + + prepareValuesGet := func(passwordRW, salt, htpasswd string) { + values.GetMock.When(passwordRWValuePath).Then(gjson.Result{Type: gjson.String, Str: passwordRW}) + values.GetMock.When(saltValuePath).Then(gjson.Result{Type: gjson.String, Str: salt}) + values.GetMock.When(htpasswdValuePath).Then(gjson.Result{Type: gjson.String, Str: htpasswd}) + } + + setSnapshots := func(snaps ...pkg.Snapshot) { + snapshots.GetMock.When(dvcrSecrets).Then(snaps) + } + + newSnapshot := func(passwordRW, salt, htpasswd string) pkg.Snapshot { + return mock.NewSnapshotMock(GinkgoT()).UnmarshalToMock.Set(func(v any) (err error) { + data, ok := v.(*dvcrSecretData) + Expect(ok).To(BeTrue()) + + data.PasswordRW = passwordRW + data.Salt = salt + data.Htpasswd = htpasswd + return nil + }) + } + + newInput := func() *pkg.HookInput { + return &pkg.HookInput{ + Snapshots: snapshots, + Values: values, + DC: dc, + Logger: log.NewNop(), + } + } + + BeforeEach(func() { + dc = mock.NewDependencyContainerMock(GinkgoT()) + snapshots = mock.NewSnapshotsMock(GinkgoT()) + values = mock.NewPatchableValuesCollectorMock(GinkgoT()) + }) + + AfterEach(func() { + dc = nil + snapshots = nil + values = nil + }) + + It("Should set secrets from secret to values", func() { + prepareValuesGet("", "", "") + setSnapshots(newSnapshot(defaultPassword, defaultSalt, defaultHtpasswd)) + + values.SetMock.Set(func(path string, v any) { + value, ok := v.(string) + Expect(ok).To(BeTrue()) + + switch path { + case passwordRWValuePath: + Expect(value).To(Equal(defaultPasswordBase64)) + case saltValuePath: + Expect(value).To(Equal(defaultSaltBase64)) + case htpasswdValuePath: + Expect(value).To(Equal(defaultHtpasswdBase64)) + default: + Fail("unexpected path") + } + }) + + Expect(handlerDVVCRSecrets(context.Background(), newInput())).To(Succeed()) + }) + + It("Should regenerate all secrets", func() { + prepareValuesGet("", "", "") + setSnapshots(newSnapshot("", "", "")) + + var ( + passwordRW string + htpasswd string + ) + + values.SetMock.Set(func(path string, v any) { + valueBase64, ok := v.(string) + Expect(ok).To(BeTrue()) + + bytes, err := base64.StdEncoding.DecodeString(valueBase64) + Expect(err).ToNot(HaveOccurred()) + + value := string(bytes) + + switch path { + case passwordRWValuePath: + passwordRW = value + Expect(value).To(HaveLen(32)) + case saltValuePath: + Expect(value).To(HaveLen(32)) + case htpasswdValuePath: + htpasswd = value + Expect(len(value)).To(BeNumerically(">", 0)) + default: + Fail("unexpected path") + } + }) + + Expect(handlerDVVCRSecrets(context.Background(), newInput())).To(Succeed()) + Expect(validateHtpasswd(passwordRW, htpasswd)).To(BeTrue()) + }) + + It("Should regenerate only salt", func() { + prepareValuesGet(defaultPasswordBase64, "", defaultHtpasswdBase64) + setSnapshots(newSnapshot(defaultPassword, "", defaultHtpasswd)) + + values.SetMock.Set(func(path string, v any) { + valueBase64, ok := v.(string) + Expect(ok).To(BeTrue()) + + bytes, err := base64.StdEncoding.DecodeString(valueBase64) + Expect(err).ToNot(HaveOccurred()) + + value := string(bytes) + + switch path { + case saltValuePath: + Expect(value).To(HaveLen(32)) + default: + Fail("unexpected path") + } + }) + + Expect(handlerDVVCRSecrets(context.Background(), newInput())).To(Succeed()) + }) + + DescribeTable("Should regenerate only password and htpasswd", func(passwordRWValue, passwordRWSnap, htpasswdValue, httpasswdSnap string) { + prepareValuesGet(passwordRWValue, "salt", htpasswdValue) + setSnapshots(newSnapshot(passwordRWSnap, "salt", httpasswdSnap)) + + var ( + passwordRW = passwordRWSnap + htpasswd = httpasswdSnap + ) + + values.SetMock.Set(func(path string, v any) { + valueBase64, ok := v.(string) + Expect(ok).To(BeTrue()) + + bytes, err := base64.StdEncoding.DecodeString(valueBase64) + Expect(err).ToNot(HaveOccurred()) + + value := string(bytes) + + switch path { + case passwordRWValuePath: + passwordRW = value + Expect(value).To(HaveLen(32)) + case htpasswdValuePath: + htpasswd = value + Expect(len(value)).To(BeNumerically(">", 0)) + default: + Fail("unexpected path") + } + }) + + Expect(handlerDVVCRSecrets(context.Background(), newInput())).To(Succeed()) + Expect(validateHtpasswd(passwordRW, htpasswd)).To(BeTrue()) + }, + Entry("with empty htpasswd and password", "", "", "", ""), + Entry("with empty password", "", "", defaultHtpasswdBase64, defaultHtpasswd), + Entry("with empty htpasswd", defaultPasswordBase64, defaultPassword, "", ""), + Entry("with not valid htpasswd", defaultPasswordBase64, defaultPassword, "bm90X3ZhbGlkX2h0cGFzc3dkCg==", "bm90X3ZhbGlkX2h0cGFzc3dkCg=="), + ) +}) + +var _ = Describe("Generate secrets", func() { + It("Should generate valid secrets", func() { + password := alphaNum(32) + Expect(password).To(HaveLen(32)) + salt := alphaNum(32) + Expect(salt).To(HaveLen(32)) + htpasswd, err := generateHtpasswd(password) + Expect(err).ToNot(HaveOccurred()) + Expect(validateHtpasswd(password, htpasswd)).To(BeTrue()) + }) +}) diff --git a/images/hooks/cmd/007-discovery-clusterip-service-for-dvcr/main.go b/images/hooks/cmd/007-discovery-clusterip-service-for-dvcr/main.go new file mode 100644 index 0000000000..fa795ebefd --- /dev/null +++ b/images/hooks/cmd/007-discovery-clusterip-service-for-dvcr/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "context" + "fmt" + + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/pkg/app" + "github.com/deckhouse/module-sdk/pkg/registry" + + "hooks/pkg/common" +) + +const ( + discoveryService = "discovery-service" + serviceName = "dvcr" + + serviceIPValuePath = "virtualization.internal.dvcr.serviceIP" +) + +var _ = registry.RegisterFunc(configDiscoveryService, handleDiscoveryService) + +var configDiscoveryService = &pkg.HookConfig{ + OnBeforeHelm: &pkg.OrderedConfig{Order: 5}, + Kubernetes: []pkg.KubernetesConfig{ + { + Name: discoveryService, + APIVersion: "v1", + Kind: "Service", + JqFilter: ".spec.clusterIP", + + NameSelector: &pkg.NameSelector{ + MatchNames: []string{serviceName}, + }, + + NamespaceSelector: &pkg.NamespaceSelector{ + NameSelector: &pkg.NameSelector{ + MatchNames: []string{common.MODULE_NAMESPACE}, + }, + }, + }, + }, + + Queue: fmt.Sprintf("modules/%s", common.MODULE_NAME), +} + +func handleDiscoveryService(_ context.Context, input *pkg.HookInput) error { + clusterIP := getClusterIP(input) + + if clusterIP == "" { + input.Logger.Info(fmt.Sprintf("ClusterIP of service dvcr not found. Delete value from %s", serviceIPValuePath)) + input.Values.Remove(serviceIPValuePath) + return nil + } + + oldClusterIP := input.Values.Get(serviceIPValuePath).String() + if clusterIP != oldClusterIP { + input.Logger.Info(fmt.Sprintf("Set ip %s to %s", clusterIP, serviceIPValuePath)) + input.Values.Set(serviceIPValuePath, clusterIP) + } + return nil +} + +func getClusterIP(input *pkg.HookInput) string { + snapshots := input.Snapshots.Get(discoveryService) + if len(snapshots) > 0 { + return snapshots[0].String() + } + return "" +} + +func main() { + app.Run() +} diff --git a/images/hooks/cmd/007-discovery-clusterip-service-for-dvcr/main_test.go b/images/hooks/cmd/007-discovery-clusterip-service-for-dvcr/main_test.go new file mode 100644 index 0000000000..841fb09b85 --- /dev/null +++ b/images/hooks/cmd/007-discovery-clusterip-service-for-dvcr/main_test.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "testing" + + "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/testing/mock" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/tidwall/gjson" +) + +func TestDiscoveryClusterIPServiceForDVCR(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "DiscoveryClusterIPServiceForDVCR Suite") +} + +var _ = Describe("DiscoveryClusterIPServiceForDVCR", func() { + var ( + dc *mock.DependencyContainerMock + snapshots *mock.SnapshotsMock + values *mock.PatchableValuesCollectorMock + ) + + BeforeEach(func() { + dc = mock.NewDependencyContainerMock(GinkgoT()) + snapshots = mock.NewSnapshotsMock(GinkgoT()) + values = mock.NewPatchableValuesCollectorMock(GinkgoT()) + }) + + AfterEach(func() { + dc = nil + snapshots = nil + values = nil + }) + + setSnapshots := func(snaps ...pkg.Snapshot) { + snapshots.GetMock.When(discoveryService).Then(snaps) + } + + newSnapshot := func(clusterIP string) pkg.Snapshot { + return mock.NewSnapshotMock(GinkgoT()).StringMock.Set(func() (s1 string) { + return clusterIP + }) + } + + newInput := func() *pkg.HookInput { + return &pkg.HookInput{ + Snapshots: snapshots, + Values: values, + DC: dc, + Logger: log.NewNop(), + } + } + + It("should set serviceIP to values", func() { + setSnapshots(newSnapshot("10.0.0.1")) + values.GetMock.When(serviceIPValuePath).Then(gjson.Result{Type: gjson.String, Str: ""}) + values.SetMock.Set(func(path string, v any) { + Expect(path).To(Equal(serviceIPValuePath)) + Expect(v).To(Equal("10.0.0.1")) + }) + Expect(handleDiscoveryService(context.Background(), newInput())).To(Succeed()) + }) + + It("Should delete serviceIP from values", func() { + setSnapshots(newSnapshot("")) + values.RemoveMock.Set(func(path string) { + Expect(path).To(Equal(serviceIPValuePath)) + }) + Expect(handleDiscoveryService(context.Background(), newInput())).To(Succeed()) + }) +}) diff --git a/images/hooks/go.mod b/images/hooks/go.mod index 9b275b4a71..1a90f8efc8 100644 --- a/images/hooks/go.mod +++ b/images/hooks/go.mod @@ -1,13 +1,19 @@ module hooks -go 1.22.10 +go 1.24 + +tool github.com/onsi/ginkgo/v2/ginkgo require ( - github.com/deckhouse/module-sdk v0.1.1-0.20250131075458-c7b4be9749d7 + github.com/deckhouse/deckhouse/pkg/log v0.0.0-20250424095005-9ab587d01d7a + github.com/deckhouse/module-sdk v0.2.2 github.com/deckhouse/virtualization/api v0.15.0 + github.com/onsi/ginkgo/v2 v2.17.1 + github.com/onsi/gomega v1.32.0 github.com/tidwall/gjson v1.14.4 - k8s.io/api v0.29.8 - k8s.io/apimachinery v0.29.8 + golang.org/x/crypto v0.35.0 + k8s.io/api v0.30.11 + k8s.io/apimachinery v0.30.11 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 ) @@ -19,7 +25,6 @@ require ( github.com/cloudflare/cfssl v1.6.5 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241205040953-7b376bae249c // indirect github.com/docker/cli v27.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect @@ -31,15 +36,17 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.5 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/gojuno/minimock/v3 v3.4.3 // indirect + github.com/gojuno/minimock/v3 v3.4.5 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/certificate-transparency-go v1.1.7 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry v0.17.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -75,29 +82,28 @@ require ( github.com/weppos/publicsuffix-go v0.30.0 // indirect github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300 // indirect github.com/zmap/zlint/v3 v3.5.0 // indirect - golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/net v0.36.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.8.0 // indirect + golang.org/x/tools v0.23.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.29.8 // indirect - k8s.io/client-go v0.29.8 // indirect - k8s.io/component-base v0.29.8 // indirect + k8s.io/apiextensions-apiserver v0.30.11 // indirect + k8s.io/client-go v0.30.11 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect kubevirt.io/api v1.0.0 // indirect kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect - sigs.k8s.io/controller-runtime v0.17.0 // indirect + sigs.k8s.io/controller-runtime v0.18.7 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/images/hooks/go.sum b/images/hooks/go.sum index 38dceb7e44..cd561d7f6a 100644 --- a/images/hooks/go.sum +++ b/images/hooks/go.sum @@ -29,10 +29,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241205040953-7b376bae249c h1:dK30IW9uGg0DvSy+IcdQ6zwEBRV55R7tEtaruEKYkSA= -github.com/deckhouse/deckhouse/pkg/log v0.0.0-20241205040953-7b376bae249c/go.mod h1:Mk5HRzkc5pIcDIZ2JJ6DPuuqnwhXVkb3you8M8Mg+4w= -github.com/deckhouse/module-sdk v0.1.1-0.20250131075458-c7b4be9749d7 h1:gqB73GoCcyRvjBEXbgcCwla5/Jtxlov/T2crQQFUvW0= -github.com/deckhouse/module-sdk v0.1.1-0.20250131075458-c7b4be9749d7/go.mod h1:xZuqvKXZunp9VNAiF70fgYiN/HQkLDo8tvGymXNpu0o= +github.com/deckhouse/deckhouse/pkg/log v0.0.0-20250424095005-9ab587d01d7a h1:c4CKIIsfpCn9wzMziOPfrqptAuHLCtTPM/heobSxPfk= +github.com/deckhouse/deckhouse/pkg/log v0.0.0-20250424095005-9ab587d01d7a/go.mod h1:pbAxTSDcPmwyl3wwKDcEB3qdxHnRxqTV+J0K+sha8bw= +github.com/deckhouse/module-sdk v0.2.2 h1:IzfWxmHiA0+F+R82qPr/JGef2kWBaXBu4tO0uo0Qzxw= +github.com/deckhouse/module-sdk v0.2.2/go.mod h1:Ps92/zmKfkRMJF79/uxCba94ldvqtsV00sUCbTFj4zw= github.com/deckhouse/virtualization/api v0.15.0 h1:yRX6n18kK9wwO2f+8fc2s2nb1N2vYKxKb3/aSRtc9Kk= github.com/deckhouse/virtualization/api v0.15.0/go.mod h1:t+6i4NC43RfNLqcZqkEc5vxY1ypKceqmOOKlVEq0cYA= github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= @@ -92,8 +92,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/gojuno/minimock/v3 v3.4.3 h1:CGH14iGxTd6kW6ZetOA/teusRN710VQ2nq8SdEuI3OQ= -github.com/gojuno/minimock/v3 v3.4.3/go.mod h1:b+hbQhEU0Csi1eyzpvi0LhlmjDHyCDPzwhXbDaKTSrQ= +github.com/gojuno/minimock/v3 v3.4.5 h1:Jcb0tEYZvVlQNtAAYpg3jCOoSwss2c1/rNugYTzj304= +github.com/gojuno/minimock/v3 v3.4.5/go.mod h1:o9F8i2IT8v3yirA7mmdpNGzh1WNesm6iQakMtQV6KiE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -121,8 +121,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk= github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -208,18 +208,17 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= -github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= +github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -329,8 +328,8 @@ golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= @@ -369,8 +368,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -382,8 +381,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -416,15 +415,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 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= @@ -434,8 +433,8 @@ 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -511,18 +510,16 @@ 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.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.29.8 h1:ZBKg9clWnIGtQ5yGhNwMw2zyyrsIAQaXhZACcYNflQE= -k8s.io/api v0.29.8/go.mod h1:XlGIpmpzKGrtVca7GlgNryZJ19SvQdI808NN7fy1SgQ= -k8s.io/apiextensions-apiserver v0.29.8 h1:VkyGgClTTWs8i81O13wsTLSs9Q1PWVr0L880F2GjwUI= -k8s.io/apiextensions-apiserver v0.29.8/go.mod h1:e6dPglIfPWm9ydsXuNqefecEVDH0uLfzClJEupSk2VU= +k8s.io/api v0.30.11 h1:TpkiTTxQ6GSwHnqKOPeQRRFcBknTjOBwFYjWmn25Z1U= +k8s.io/api v0.30.11/go.mod h1:DZzjCDcat14fMx/4Fm3h5lsbVStfHmgNzNDMy7JQMqU= +k8s.io/apiextensions-apiserver v0.30.11 h1:r0boYooz99DF6wn+myaK92I6oe80DmWSv8gueP+mcWc= +k8s.io/apiextensions-apiserver v0.30.11/go.mod h1:d8Fje2TL9Oc6F48Y84ZlSCP0KQOOM5P6AYoK38WLeCE= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.29.8 h1:uBHc9WuKiTHClIspJqtR84WNpG0aOGn45HWqxgXkk8Y= -k8s.io/apimachinery v0.29.8/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= -k8s.io/client-go v0.29.8 h1:QMRKcIzqE/qawknXcsi51GdIAYN8UP39S/M5KnFu/J0= -k8s.io/client-go v0.29.8/go.mod h1:ZzrAAVrqO2jVXMb8My/jTke8n0a/mIynnA3y/1y1UB0= +k8s.io/apimachinery v0.30.11 h1:+qV/yXI2R7BxX1zeyELDFb0PopX22znfq5w+icav49k= +k8s.io/apimachinery v0.30.11/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.11 h1:yamC5zf/g5ztZO3SELklaOSZKTOAL3Q0v0i6GBvq+Mg= +k8s.io/client-go v0.30.11/go.mod h1:umPRna4oj2zLU03T1m7Cla+yMzRFyhuR+jAbDZNDqlM= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/component-base v0.29.8 h1:4LJ94/eOJpDFZFbGbRH4CEyk29a7PZr8noVe9tBJUUY= -k8s.io/component-base v0.29.8/go.mod h1:FYOQSsKgh9/+FNleq8m6cXH2Cq8fNiUnJzDROowLaqU= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= @@ -545,8 +542,8 @@ kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 h1:IWo12+ei3jltSN5jQN kubevirt.io/containerized-data-importer-api v1.57.0-alpha1/go.mod h1:Y/8ETgHS1GjO89bl682DPtQOYEU/1ctPFBz6Sjxm4DM= kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc= kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= -sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= -sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= +sigs.k8s.io/controller-runtime v0.18.7 h1:WDnx8LTRY8Fn1j/7B+S/R9MeDjWNAzpDBoaSvMSrQME= +sigs.k8s.io/controller-runtime v0.18.7/go.mod h1:L9r3fUZhID7Q9eK9mseNskpaTg2n11f/tlb8odyzJ4Y= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= diff --git a/images/hooks/werf.inc.yaml b/images/hooks/werf.inc.yaml index af14bacb2f..066dee6026 100644 --- a/images/hooks/werf.inc.yaml +++ b/images/hooks/werf.inc.yaml @@ -1,7 +1,7 @@ --- image: {{ $.ImageName }} final: false -fromImage: BASE_GOLANG_23_BOOKWORM +fromImage: BASE_GOLANG_24_BOOKWORM git: - add: /images/{{ $.ImageName }} to: /app/images/hooks @@ -28,4 +28,6 @@ shell: - go build -ldflags="-s -w" -o /hooks/002-tls-certificates-dvcr ./cmd/002-tls-certificates-dvcr - go build -ldflags="-s -w" -o /hooks/003-tls-certificates-api ./cmd/003-tls-certificates-api - go build -ldflags="-s -w" -o /hooks/004-tls-certificates-api-proxy ./cmd/004-tls-certificates-api-proxy - - go build -ldflags="-s -w" -o /hooks/005-prevent-default-vmclasses-deletion ./cmd/005-prevent-default-vmclasses-deletion \ No newline at end of file + - go build -ldflags="-s -w" -o /hooks/005-prevent-default-vmclasses-deletion ./cmd/005-prevent-default-vmclasses-deletion + - go build -ldflags="-s -w" -o /hooks/006-generate-secret-for-dvcr ./cmd/006-generate-secret-for-dvcr + - go build -ldflags="-s -w" -o /hooks/007-discovery-clusterip-service-for-dvcr ./cmd/007-discovery-clusterip-service-for-dvcr