From 8866b578abbe9aca46236e8eba112a3701218fc2 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Sat, 27 Sep 2025 00:49:56 +1000 Subject: [PATCH 01/21] Change prettier pre-commit to a local hook (#4779) The old prettier hook repository has been archived for over a year: https://github.com/pre-commit/mirrors-prettier Use the most current versions of Node.js and prettier: https://www.npmjs.com/package/prettier https://nodejs.org/en/download prettier uses an ignore file to exclude files so add "schema.json" You can see a prettier local hook that I added to mruby here: https://github.com/mruby/mruby/blob/master/.pre-commit-config.yaml#L22 Signed-off-by: John Bampton --- .pre-commit-config.yaml | 19 ++++++++++--------- .prettierignore | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a891e6c5fc33..00c686f8240d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,8 +2,8 @@ # https://pre-commit.com/ default_stages: [pre-commit, pre-push] default_language_version: - # force all unspecified Python hooks to run python3 python: python3 + node: 22.19.0 minimum_pre_commit_version: '3.2.0' repos: - repo: meta @@ -12,6 +12,15 @@ repos: name: run identity check - id: check-hooks-apply name: run check-hooks-apply + - repo: local + hooks: + - id: prettier + name: run prettier + description: format files with prettier + entry: prettier --write '**/*.json' '**/*.md' '**/*.yaml' '**/*.yml' + files: \.(json|md|ya?ml)$ + language: node + additional_dependencies: ['prettier@3.6.2'] # general checks for the repository - repo: https://github.com/gitleaks/gitleaks rev: v8.28.0 @@ -65,14 +74,6 @@ repos: description: Check Spelling with codespell entry: codespell -L ba,flate,ges,indx,keypair,splitted,vertexes --exclude-file=go.sum - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 - hooks: - - id: prettier - name: run prettier - description: Format files with prettier - exclude: schema.json - # yaml files check - repo: https://github.com/adrienverge/yamllint rev: v1.37.1 diff --git a/.prettierignore b/.prettierignore index 25b7d30854d1..f0d19052173f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ .github/** /docs/** integration/validate/manifests/invalid-syntax.yml +schema.json From 7f8dd8f372d82ed24d0b7c0231944e6b69ffac0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Mon, 29 Sep 2025 16:08:02 +0200 Subject: [PATCH 02/21] fix: cves (#4787) Signed-off-by: Javier Lopez --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3245ad293118..bb95d98ce36a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # Base image versions - Centralized version control for easier updates # Kubernetes tools -ARG KUBECTL_VERSION=1.33.4 +ARG KUBECTL_VERSION=1.33.5 ARG HELM_VERSION=3.18.6 ARG KUSTOMIZE_VERSION=5.7.1 # Okteto components From 484153e173dbcf8b9d3a0642a8cd05a69a99e974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Tue, 30 Sep 2025 12:47:43 +0200 Subject: [PATCH 03/21] Fix Cache tests (#4785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix test container cache isolation issue Test containers now use unique cache IDs based on manifest name and test context, preventing cache sharing between different test containers that could lead to inconsistent behavior. Changes: - Modified dockerfile template to generate unique cache IDs using format: {manifestName}-{testName}-{cacheIndex} - Updated remote.Params struct to include TestName field for cache isolation - Updated cmd/test to pass test name to remote execution parameters - Added comprehensive tests to verify cache isolation between different test contexts and manifests - Ensures caches are independent by okteto manifest name and test context as required 🤖 Generated with [Okteto AI Powered by Claude Code](https://claude.ai/code) Co-Authored-By: Okteto AI Powered by Claude Code * add repo hash Signed-off-by: Javier Lopez * fix: address comments Signed-off-by: Javier Lopez * fic: test Signed-off-by: Javier Lopez --------- Signed-off-by: Javier Lopez Co-authored-by: oktetobot Co-authored-by: Okteto AI Powered by Claude Code --- cmd/test/cmd.go | 1 + pkg/remote/remote.go | 44 +++++++++++++- pkg/remote/remote_test.go | 117 +++++++++++++++++++++++++++++++++++++- 3 files changed, 158 insertions(+), 4 deletions(-) diff --git a/cmd/test/cmd.go b/cmd/test/cmd.go index ab96864bf28d..70ec64694fc5 100644 --- a/cmd/test/cmd.go +++ b/cmd/test/cmd.go @@ -387,6 +387,7 @@ func doRun(ctx context.Context, servicesToTest []string, options *Options, ioCtr Manifest: manifest, Command: remote.TestCommand, ContextAbsolutePathOverride: ctxCwd, + TestName: name, Caches: test.Caches, IgnoreRules: testIgnoreRules, Artifacts: test.Artifacts, diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index 386aa294d6cd..c762a07ba9e5 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -16,7 +16,9 @@ package remote import ( "context" "crypto/rand" + "crypto/sha256" "encoding/base64" + "encoding/hex" "errors" "fmt" "math/big" @@ -39,6 +41,7 @@ import ( "github.com/okteto/okteto/pkg/log/io" "github.com/okteto/okteto/pkg/model" "github.com/okteto/okteto/pkg/okteto" + "github.com/okteto/okteto/pkg/repository" "github.com/okteto/okteto/pkg/types" "github.com/spf13/afero" "gopkg.in/yaml.v2" @@ -100,7 +103,7 @@ RUN echo "${{ .InvalidateCacheArgName }}" > /etc/.oktetocachekey RUN okteto registrytoken install --force --log-output=json RUN \ - {{range $key, $path := .Caches }}--mount=type=cache,target={{$path}},sharing=private {{end}}\ + {{range $key, $path := .Caches }}--mount=type=cache,id={{cacheID $.RepositoryURL $.ManifestName $.TestName $path}},target={{$path}},sharing=private {{end}}\ --mount=type=secret,id=known_hosts \ --mount=type=secret,id={{ .SSHAgentSocketArgName }},env=SSH_AUTH_SOCK \ mkdir -p $HOME/.ssh && echo "UserKnownHostsFile=/run/secrets/known_hosts $HOME/.ssh/known_hosts" >> $HOME/.ssh/config && \ @@ -177,6 +180,8 @@ type Params struct { CommandFlags []string Caches []string Hosts []model.Host + // TestName is the name of the test container, used for cache isolation + TestName string // IgnoreRules are the ignoring rules added to this build execution. // Rules follow the .dockerignore syntax as defined in: // https://docs.docker.com/build/building/context/#syntax @@ -220,6 +225,9 @@ type dockerfileTemplateProperties struct { SSHAgentPort string SSHAgentSocketArgName string Caches []string + TestName string + ManifestName string + RepositoryURL string Artifacts []model.Artifact } @@ -461,9 +469,24 @@ func (r *Runner) createDockerfile(tmpDir string, params *Params) (string, error) return "", err } + // Get repository URL for cache ID generation + repoURL := "" + if params.Manifest != nil && params.Manifest.ManifestPath != "" { + repo := repository.NewRepository(params.Manifest.ManifestPath) + repoURL = repo.GetAnonymizedRepo() + } + if repoURL == "" { + repoURL = params.Manifest.ManifestPath + } + tmpl := template. Must(template.New(params.TemplateName). - Funcs(template.FuncMap{"join": strings.Join}). + Funcs(template.FuncMap{ + "join": strings.Join, + "cacheID": func(repoURL, manifestName, testName, path string) string { + return generateCacheID(repoURL, manifestName, testName, path) + }, + }). Parse(dockerfileTemplate)) dockerfileSyntax := dockerfileTemplateProperties{ @@ -490,6 +513,9 @@ func (r *Runner) createDockerfile(tmpDir string, params *Params) (string, error) OktetoCommandSpecificEnvVars: params.OktetoCommandSpecificEnvVars, Command: params.Command, Caches: params.Caches, + TestName: params.TestName, + ManifestName: params.Manifest.Name, + RepositoryURL: repoURL, Artifacts: params.Artifacts, SSHAgentHostname: params.SSHAgentHostname, SSHAgentHostnameArgName: constants.OktetoSshAgentHostnameEnvVar, @@ -688,6 +714,20 @@ func formatEnvVarValueForDocker(value string) string { return result.String() } +// generateCacheID creates a unique cache identifier based on repository, manifest name, and test name +func generateCacheID(repositoryURL, manifestName, testName, path string) string { + // Create input string for hashing + input := fmt.Sprintf("%s-%s-%s-%s", repositoryURL, manifestName, testName, path) + + // Generate SHA256 hash + hasher := sha256.New() + hasher.Write([]byte(input)) + hash := hasher.Sum(nil) + + // Return first 12 characters of hex hash for readability + return hex.EncodeToString(hash)[:12] +} + func generateRandomSocketNameString() string { random := strings.ReplaceAll(namesgenerator.GetRandomName(-1), "_", "-") return fmt.Sprintf("/tmp/okteto-%s.sock", random) diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go index 5c80f4360305..eb84601bd9ba 100644 --- a/pkg/remote/remote_test.go +++ b/pkg/remote/remote_test.go @@ -573,22 +573,135 @@ func TestDockerfileWithCache(t *testing.T) { generateSocketName: nameGenerator.GenerateName, } caches := []string{"/my", "/cache", "/list"} + manifest := &model.Manifest{Name: "test-manifest"} dockerfileName, err := rdc.createDockerfile("/test", &Params{ Caches: caches, DockerfileName: "myDockerfile", + TestName: "unit-test", + Manifest: manifest, }) require.NoError(t, err) require.Equal(t, filepath.Clean("/test/myDockerfile"), dockerfileName) d, err := afero.ReadFile(fs, dockerfileName) require.NoError(t, err) + + // Verify that each cache has a unique ID based on manifest name, test name, and cache index for _, cache := range caches { - pattern := fmt.Sprintf("--mount=type=cache,target=%s", cache) + pattern := fmt.Sprintf("--mount=type=cache,id=\\w+,target=%s", cache) ok, err := regexp.MatchString(pattern, string(d)) require.NoError(t, err) - require.True(t, ok) + require.True(t, ok, "Expected cache mount pattern not found: %s", pattern) + } +} + +func TestCacheIsolationBetweenTestContexts(t *testing.T) { + wdCtrl := filesystem.NewFakeWorkingDirectoryCtrl(filepath.Clean("/")) + nameGenerator := fakeNameGenerator{ + name: "okteto-socket.sock", + } + fs := afero.NewMemMapFs() + rdc := Runner{ + fs: fs, + workingDirectoryCtrl: wdCtrl, + getEnviron: func() []string { + return []string{} + }, + ioCtrl: io.NewIOController(), + generateSocketName: nameGenerator.GenerateName, + } + caches := []string{"/root/.cache", "/go/pkg/mod"} + + // Test case 1: manifest1, test1 + manifest1 := &model.Manifest{Name: "app1"} + dockerfile1, err := rdc.createDockerfile("/test1", &Params{ + Caches: caches, + DockerfileName: "Dockerfile.test1", + TestName: "unit", + Manifest: manifest1, + }) + require.NoError(t, err) + + // Test case 2: manifest1, test2 (different test context) + dockerfile2, err := rdc.createDockerfile("/test2", &Params{ + Caches: caches, + DockerfileName: "Dockerfile.test2", + TestName: "integration", + Manifest: manifest1, + }) + require.NoError(t, err) + + // Test case 3: manifest2, test1 (different manifest) + manifest2 := &model.Manifest{Name: "app2"} + dockerfile3, err := rdc.createDockerfile("/test3", &Params{ + Caches: caches, + DockerfileName: "Dockerfile.test3", + TestName: "unit", + Manifest: manifest2, + }) + require.NoError(t, err) + + // Read all dockerfiles + d1, err := afero.ReadFile(fs, dockerfile1) + require.NoError(t, err) + d2, err := afero.ReadFile(fs, dockerfile2) + require.NoError(t, err) + d3, err := afero.ReadFile(fs, dockerfile3) + require.NoError(t, err) + + // Capture the actual cache IDs generated for each dockerfile + cacheIDs1 := extractCacheIDs(string(d1), caches) + cacheIDs2 := extractCacheIDs(string(d2), caches) + cacheIDs3 := extractCacheIDs(string(d3), caches) + + // Verify that we found cache IDs for all cache paths + require.Len(t, cacheIDs1, len(caches), "Dockerfile1 should have cache IDs for all cache paths") + require.Len(t, cacheIDs2, len(caches), "Dockerfile2 should have cache IDs for all cache paths") + require.Len(t, cacheIDs3, len(caches), "Dockerfile3 should have cache IDs for all cache paths") + + // Verify that each test context generates unique cache IDs + for _, cache := range caches { + id1, exists1 := cacheIDs1[cache] + require.True(t, exists1, "Dockerfile1 should have cache ID for %s", cache) + + id2, exists2 := cacheIDs2[cache] + require.True(t, exists2, "Dockerfile2 should have cache ID for %s", cache) + + id3, exists3 := cacheIDs3[cache] + require.True(t, exists3, "Dockerfile3 should have cache ID for %s", cache) + + // Ensure that the cache IDs are different between contexts + require.NotEqual(t, id1, id2, "Cache IDs should be different between test contexts for cache %s (%s vs %s)", cache, id1, id2) + require.NotEqual(t, id1, id3, "Cache IDs should be different between manifests for cache %s (%s vs %s)", cache, id1, id3) + require.NotEqual(t, id2, id3, "Cache IDs should be different between different manifest-test combinations for cache %s (%s vs %s)", cache, id2, id3) + + // Verify that cache IDs are non-empty and reasonable length (should be SHA256 hash prefixes) + require.NotEmpty(t, id1, "Cache ID for dockerfile1, cache %s should not be empty", cache) + require.NotEmpty(t, id2, "Cache ID for dockerfile2, cache %s should not be empty", cache) + require.NotEmpty(t, id3, "Cache ID for dockerfile3, cache %s should not be empty", cache) + require.Greater(t, len(id1), 8, "Cache ID should be at least 8 characters long") + require.Greater(t, len(id2), 8, "Cache ID should be at least 8 characters long") + require.Greater(t, len(id3), 8, "Cache ID should be at least 8 characters long") } } +// extractCacheIDs extracts cache IDs from dockerfile content and maps them to their target paths +func extractCacheIDs(dockerfileContent string, cachePaths []string) map[string]string { + cacheIDs := make(map[string]string) + + for _, cachePath := range cachePaths { + // Look for pattern: --mount=type=cache,id=SOME_ID,target=CACHE_PATH + pattern := fmt.Sprintf(`--mount=type=cache,id=([^,]+),target=%s`, regexp.QuoteMeta(cachePath)) + re := regexp.MustCompile(pattern) + + matches := re.FindStringSubmatch(dockerfileContent) + if len(matches) > 1 { + cacheIDs[cachePath] = matches[1] + } + } + + return cacheIDs +} + func TestGetExtraHosts(t *testing.T) { registryURL := "registry.test.dev.okteto.net" subdomain := "test.dev.okteto.net" From 7201a52a167242a7c03de2dd7b50de56c89f69ad Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Thu, 2 Oct 2025 21:56:47 +0800 Subject: [PATCH 04/21] build(deps): upgrade github.com/shirou/gopsutil to v4 (#4790) v4 is the latest version with bug fixes and enhancements, notably the removal of CGO implementations on Darwin in v4.24.9 [1]. Note: Due to the library's versioning policy, v4.24.5 is the first v4 release [2]. [1]: https://github.com/shirou/gopsutil/releases/tag/v4.24.9 [2]: https://github.com/shirou/gopsutil/releases/tag/v4.24.5 Signed-off-by: Eng Zer Jun --- go.mod | 15 +++++++++------ go.sum | 32 +++++++++++++++++++------------- pkg/syncthing/process.go | 2 +- pkg/syncthing/syncthing.go | 2 +- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 85286de6e057..f9a08542114c 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/moby/buildkit v0.18.2 github.com/moby/term v0.5.2 github.com/pkg/errors v0.9.1 - github.com/shirou/gopsutil v3.21.11+incompatible + github.com/shirou/gopsutil/v4 v4.25.9 github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 github.com/sirupsen/logrus v1.9.3 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 @@ -134,9 +134,9 @@ require ( github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/spf13/pflag v1.0.5 github.com/src-d/go-oniguruma v1.1.0 // indirect - github.com/stretchr/testify v1.10.0 - github.com/tklauser/go-sysconf v0.3.9 // indirect - github.com/tklauser/numcpus v0.3.0 // indirect + github.com/stretchr/testify v1.11.1 + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect @@ -145,7 +145,7 @@ require ( github.com/withfig/autocomplete-tools/packages/cobra v0.0.0-20211118163844-94616c903bcb github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xlab/treeprint v1.2.0 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect @@ -155,7 +155,7 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/net v0.39.0 // indirect - golang.org/x/sys v0.33.0 // indirect + golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/api v0.162.0 // indirect @@ -195,12 +195,15 @@ require ( github.com/bodgit/windows v1.0.1 // indirect github.com/containerd/containerd/v2 v2.1.3 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/ebitengine/purego v0.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/sorairolake/lzip-go v0.3.5 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/x448/float16 v0.8.4 // indirect diff --git a/go.sum b/go.sum index ad40a2e7e164..55333b91297a 100644 --- a/go.sum +++ b/go.sum @@ -783,6 +783,8 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf github.com/dukex/mixpanel v0.0.0-20180925151559-f8d5594f958e h1:Rr/xguBo8FlFC/U8ekbeWDBYydqZDD6bKGT5rDlBCUU= github.com/dukex/mixpanel v0.0.0-20180925151559-f8d5594f958e/go.mod h1:AgMMmOoSoKDavirJHvIHNcaPq2S9QvZKnuN0We/Hwyo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= +github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= @@ -1083,6 +1085,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= @@ -1191,6 +1195,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= @@ -1225,8 +1231,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= -github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= -github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= +github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc= github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -1271,14 +1277,14 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= -github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI= github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= @@ -1316,8 +1322,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1593,6 +1599,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1612,7 +1619,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1651,8 +1657,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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= diff --git a/pkg/syncthing/process.go b/pkg/syncthing/process.go index 4ef937b15495..903f2b72ab74 100644 --- a/pkg/syncthing/process.go +++ b/pkg/syncthing/process.go @@ -17,7 +17,7 @@ import ( "runtime" "time" - "github.com/shirou/gopsutil/process" + "github.com/shirou/gopsutil/v4/process" ) func terminate(p *process.Process, wait bool) error { diff --git a/pkg/syncthing/syncthing.go b/pkg/syncthing/syncthing.go index 7f025240d95a..d4ad9b086283 100644 --- a/pkg/syncthing/syncthing.go +++ b/pkg/syncthing/syncthing.go @@ -37,7 +37,7 @@ import ( "github.com/okteto/okteto/pkg/filesystem" oktetoLog "github.com/okteto/okteto/pkg/log" "github.com/okteto/okteto/pkg/model" - "github.com/shirou/gopsutil/process" + "github.com/shirou/gopsutil/v4/process" "github.com/spf13/afero" "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v2" From 02918376e9cf43cc8e7e4238abf39a6af9b0d6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Fri, 3 Oct 2025 10:33:57 +0200 Subject: [PATCH 05/21] refactor: remove unused function (#4792) Signed-off-by: Javier Lopez --- pkg/build/info.go | 20 -------------- pkg/build/info_test.go | 60 ------------------------------------------ 2 files changed, 80 deletions(-) diff --git a/pkg/build/info.go b/pkg/build/info.go index 6c920038f3d8..8750417af520 100644 --- a/pkg/build/info.go +++ b/pkg/build/info.go @@ -21,9 +21,7 @@ import ( "github.com/okteto/okteto/pkg/cache" "github.com/okteto/okteto/pkg/env" - "github.com/okteto/okteto/pkg/filesystem" oktetoLog "github.com/okteto/okteto/pkg/log" - "github.com/spf13/afero" ) // Info represents the build info to generate an image @@ -219,21 +217,3 @@ func (i *Info) AddArgs(previousImageArgs map[string]string) error { } return i.addExpandedPreviousImageArgs(previousImageArgs) } - -// GetDockerfilePath returns the path to the Dockerfile -func (i *Info) GetDockerfilePath(fs afero.Fs) string { - if filepath.IsAbs(i.Dockerfile) { - return i.Dockerfile - } - joinPath := filepath.Join(i.Context, i.Dockerfile) - if !filesystem.FileExistsAndNotDir(joinPath, fs) { - oktetoLog.Infof("Dockerfile '%s' is not in a relative path to context '%s'", i.Dockerfile, i.Context) - return i.Dockerfile - } - - if joinPath != filepath.Clean(i.Dockerfile) && filesystem.FileExistsAndNotDir(i.Dockerfile, fs) { - oktetoLog.Infof("Two Dockerfiles discovered in both the root and context path, defaulting to '%s/%s'", i.Context, i.Dockerfile) - } - - return joinPath -} diff --git a/pkg/build/info_test.go b/pkg/build/info_test.go index 114fce75a966..e1ad21ea7133 100644 --- a/pkg/build/info_test.go +++ b/pkg/build/info_test.go @@ -15,13 +15,11 @@ package build import ( "fmt" - "os" "path/filepath" "runtime" "testing" "github.com/okteto/okteto/pkg/cache" - "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" @@ -181,64 +179,6 @@ func TestExpandBuildArgs(t *testing.T) { } } -func TestBuildInfo_GetDockerfilePath(t *testing.T) { - dir := t.TempDir() - - dockerfilePath := filepath.Join(dir, "Dockerfile") - dockerfiledevPath := filepath.Join(dir, "Dockerfile.dev") - assert.NoError(t, os.WriteFile(dockerfilePath, []byte(`FROM alpine`), 0600)) - assert.NoError(t, os.WriteFile(dockerfiledevPath, []byte(`FROM alpine`), 0600)) - tests := []struct { - name string - context string - dockerfile string - want string - }{ - { - name: "with-context", - context: dir, - dockerfile: "Dockerfile", - want: filepath.Join(dir, "Dockerfile"), - }, - { - name: "with-context-and-non-dockerfile", - context: dir, - dockerfile: "Dockerfile.dev", - want: filepath.Join(dir, "Dockerfile.dev"), - }, - { - name: "empty", - context: "", - dockerfile: "", - want: "", - }, - { - name: "default", - context: "", - dockerfile: "Dockerfile", - want: "Dockerfile", - }, - - { - name: "with-context-and-dockerfile-expanded", - context: "api", - dockerfile: "api/Dockerfile.dev", - want: "api/Dockerfile.dev", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &Info{ - Context: tt.context, - Dockerfile: tt.dockerfile, - } - if got := b.GetDockerfilePath(afero.NewOsFs()); got != tt.want { - t.Errorf("Info.GetDockerfilePath() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_BuildInfoCopy(t *testing.T) { b := &Info{ Context: "context", From 497391f7f8ac02cc899d196f7a4590e63e251e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Fri, 3 Oct 2025 11:13:56 +0200 Subject: [PATCH 06/21] fix: nil pointer exception in build (#4791) Signed-off-by: Javier Lopez --- pkg/filesystem/file.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/filesystem/file.go b/pkg/filesystem/file.go index d5533773234c..1c43bf578ba3 100644 --- a/pkg/filesystem/file.go +++ b/pkg/filesystem/file.go @@ -84,7 +84,8 @@ func CopyFile(from, to string) error { // FileExistsAndNotDir checks if the file exists, and it's not a dir func FileExistsAndNotDir(path string, fs afero.Fs) bool { info, err := fs.Stat(path) - if err != nil && os.IsNotExist(err) { + if err != nil { + oktetoLog.Infof("failed to check if %s is a file: %s", path, err) return false } return !info.IsDir() From e2f388ea57e65f92db0fae5aad2a804d8f77a2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Fri, 3 Oct 2025 11:56:26 +0200 Subject: [PATCH 07/21] add cache id in run cmds (#4789) * add id on dockerfile run caches Signed-off-by: Javier Lopez * add translator file Signed-off-by: Javier Lopez * fix cache mounts in run Signed-off-by: Javier Lopez * fix: one line multiple caches Signed-off-by: Javier Lopez * fix Signed-off-by: Javier Lopez * refactgor remove unused code Signed-off-by: Javier Lopez * refactor: move regex in the file var section Signed-off-by: Javier Lopez * fix: hash everything Signed-off-by: Javier Lopez * fix: multiple mount cache with several with ids Signed-off-by: Javier Lopez * fix: refactor naming Signed-off-by: Javier Lopez * add prefixes for test and build Signed-off-by: Javier Lopez * fix: add gid uid param scenario Signed-off-by: Javier Lopez * address comments Signed-off-by: Javier Lopez --------- Signed-off-by: Javier Lopez --- cmd/build/v2/build.go | 2 +- pkg/cmd/build/build.go | 21 +- pkg/cmd/build/build_test.go | 111 ++++- pkg/cmd/build/file.go | 44 +- pkg/cmd/build/translator.go | 257 +++++++++++ pkg/cmd/build/translator_test.go | 742 +++++++++++++++++++++++++++++++ pkg/remote/remote.go | 2 +- 7 files changed, 1136 insertions(+), 43 deletions(-) create mode 100644 pkg/cmd/build/translator.go create mode 100644 pkg/cmd/build/translator_test.go diff --git a/cmd/build/v2/build.go b/cmd/build/v2/build.go index 79bf8214313c..66af8d851dd8 100644 --- a/cmd/build/v2/build.go +++ b/cmd/build/v2/build.go @@ -373,7 +373,7 @@ func (bc *OktetoBuilder) buildSvcFromDockerfile(ctx context.Context, manifest *m return "", fmt.Errorf("error expanding build args from service '%s': %w", svcName, err) } - buildOptions := buildCmd.OptsFromBuildInfo(manifest.Name, svcName, buildSvcInfo, options, bc.Registry, bc.oktetoContext) + buildOptions := buildCmd.OptsFromBuildInfo(manifest, svcName, buildSvcInfo, options, bc.Registry, bc.oktetoContext) if err := bc.Builder.Build(ctx, buildOptions); err != nil { return "", err diff --git a/pkg/cmd/build/build.go b/pkg/cmd/build/build.go index 081f502422ac..18c1c9100f65 100644 --- a/pkg/cmd/build/build.go +++ b/pkg/cmd/build/build.go @@ -35,6 +35,7 @@ import ( "github.com/okteto/okteto/pkg/model" "github.com/okteto/okteto/pkg/okteto" "github.com/okteto/okteto/pkg/registry" + "github.com/okteto/okteto/pkg/repository" "github.com/okteto/okteto/pkg/types" "github.com/spf13/afero" ) @@ -129,9 +130,18 @@ func GetRegistryConfigFromOktetoConfig(okCtx OktetoContextInterface) *okteto.Con func (ob *OktetoBuilder) buildWithOkteto(ctx context.Context, buildOptions *types.BuildOptions, ioCtrl *io.Controller, run buildkit.SolveBuildFn) error { oktetoLog.Infof("building your image on %s", ob.OktetoContext.GetCurrentBuilder()) + repoURL := "" + if buildOptions.Manifest != nil && buildOptions.Manifest.ManifestPath != "" { + repo := repository.NewRepository(buildOptions.Manifest.ManifestPath) + repoURL = repo.GetAnonymizedRepo() + } + if buildOptions.Manifest != nil && repoURL == "" { + repoURL = buildOptions.Manifest.ManifestPath + } + var err error if buildOptions.File != "" { - buildOptions.File, err = GetDockerfile(buildOptions.File, ob.OktetoContext) + buildOptions.File, err = GetDockerfile(buildOptions.File, ob.OktetoContext, repoURL, buildOptions.File, buildOptions.Target) if err != nil { return err } @@ -218,7 +228,7 @@ type regInterface interface { } // OptsFromBuildInfo returns the parsed options for the build from the manifest -func OptsFromBuildInfo(manifestName, svcName string, b *build.Info, o *types.BuildOptions, reg regInterface, okCtx OktetoContextInterface) *types.BuildOptions { +func OptsFromBuildInfo(manifest *model.Manifest, svcName string, b *build.Info, o *types.BuildOptions, reg regInterface, okCtx OktetoContextInterface) *types.BuildOptions { if o == nil { o = &types.BuildOptions{} } @@ -232,8 +242,12 @@ func OptsFromBuildInfo(manifestName, svcName string, b *build.Info, o *types.Bui b.Image = o.Tag } + name := "" + if manifest != nil && manifest.Name != "" { + name = manifest.Name + } // manifestName can be not sanitized when option name is used at deploy - sanitizedName := format.ResourceK8sMetaString(manifestName) + sanitizedName := format.ResourceK8sMetaString(name) if okCtx.IsOktetoCluster() && b.Image == "" { b.Image = fmt.Sprintf("%s/%s-%s:%s", constants.DevRegistry, sanitizedName, svcName, model.OktetoDefaultImageTag) } @@ -298,6 +312,7 @@ func OptsFromBuildInfo(manifestName, svcName string, b *build.Info, o *types.Bui } opts := &types.BuildOptions{ + Manifest: manifest, CacheFrom: b.CacheFrom, Target: b.Target, Path: b.Context, diff --git a/pkg/cmd/build/build_test.go b/pkg/cmd/build/build_test.go index 77530839494b..df6b59a09e4c 100644 --- a/pkg/cmd/build/build_test.go +++ b/pkg/cmd/build/build_test.go @@ -171,6 +171,14 @@ func Test_OptsFromBuildInfo(t *testing.T) { }, isOkteto: true, expected: &types.BuildOptions{ + Manifest: &model.Manifest{ + Name: "movies", + Build: build.ManifestBuild{ + "service": { + Image: "okteto.dev/movies-service:okteto", + }, + }, + }, OutputMode: oktetoLog.TTYFormat, Tag: "okteto.dev/movies-service:okteto", BuildArgs: []string{namespaceEnvVar.String()}, @@ -182,6 +190,12 @@ func Test_OptsFromBuildInfo(t *testing.T) { buildInfo: &build.Info{}, isOkteto: false, expected: &types.BuildOptions{ + Manifest: &model.Manifest{ + Name: "movies", + Build: build.ManifestBuild{ + "service": {}, + }, + }, OutputMode: oktetoLog.TTYFormat, BuildArgs: []string{}, }, @@ -211,6 +225,24 @@ func Test_OptsFromBuildInfo(t *testing.T) { }, isOkteto: true, expected: &types.BuildOptions{ + Manifest: &model.Manifest{ + Name: "movies", + Build: build.ManifestBuild{ + "service": { + Context: serviceContext, + Dockerfile: serviceDockerfile, + Image: "okteto.dev/movies-service:okteto", + Target: "build", + CacheFrom: []string{"cache-image"}, + Args: build.Args{ + { + Name: "arg1", + Value: "value1", + }, + }, + }, + }, + }, OutputMode: oktetoLog.TTYFormat, Tag: "okteto.dev/movies-service:okteto", File: filepath.Join(dir, serviceContext, serviceDockerfile), @@ -251,6 +283,30 @@ func Test_OptsFromBuildInfo(t *testing.T) { }, isOkteto: true, expected: &types.BuildOptions{ + Manifest: &model.Manifest{ + Name: "movies", + Build: build.ManifestBuild{ + "service": { + Context: serviceContext, + Image: "okteto.dev/movies-service:okteto", + Dockerfile: serviceDockerfile, + Target: "build", + CacheFrom: []string{"cache-image"}, + Args: build.Args{ + { + Name: "arg1", + Value: "value1", + }, + }, + VolumesToInclude: []build.VolumeMounts{ + { + LocalPath: "a", + RemotePath: "b", + }, + }, + }, + }, + }, OutputMode: oktetoLog.TTYFormat, Tag: "okteto.dev/movies-service:okteto", File: filepath.Join(dir, serviceContext, serviceDockerfile), @@ -293,6 +349,29 @@ func Test_OptsFromBuildInfo(t *testing.T) { }, isOkteto: true, expected: &types.BuildOptions{ + Manifest: &model.Manifest{ + Name: "movies", + Build: build.ManifestBuild{ + "service": { + Image: "okteto.dev/mycustomimage:dev", + Context: serviceContext, + Dockerfile: serviceDockerfile, + Target: "build", + CacheFrom: []string{"cache-image"}, + Args: build.Args{ + namespaceEnvVar, + { + Name: "arg1", + Value: "value1", + }, + }, + Secrets: map[string]string{ + "mysecret": "source", + }, + ExportCache: []string{"export-image"}, + }, + }, + }, OutputMode: oktetoLog.TTYFormat, Tag: "okteto.dev/mycustomimage:dev", File: filepath.Join(dir, serviceContext, serviceDockerfile), @@ -321,6 +400,14 @@ func Test_OptsFromBuildInfo(t *testing.T) { repo: "movies-service", }, expected: &types.BuildOptions{ + Manifest: &model.Manifest{ + Name: "movies", + Build: build.ManifestBuild{ + "service": { + Image: "okteto.dev/movies-service:okteto", + }, + }, + }, BuildArgs: []string{namespaceEnvVar.String()}, Platform: "linux/amd64", Tag: "okteto.dev/movies-service:okteto", @@ -343,6 +430,14 @@ func Test_OptsFromBuildInfo(t *testing.T) { repo: "movies-service", }, expected: &types.BuildOptions{ + Manifest: &model.Manifest{ + Name: "movies", + Build: build.ManifestBuild{ + "service": { + Image: "okteto.dev/movies-service:okteto", + }, + }, + }, BuildArgs: []string{ namespaceEnvVar.String(), "arg1=value1", @@ -374,6 +469,20 @@ func Test_OptsFromBuildInfo(t *testing.T) { repo: "movies-service", }, expected: &types.BuildOptions{ + Manifest: &model.Manifest{ + Name: "movies", + Build: build.ManifestBuild{ + "service": { + Image: "okteto.dev/movies-service:okteto", + Args: build.Args{ + { + Name: "arg1", + Value: "value2", + }, + }, + }, + }, + }, BuildArgs: []string{ namespaceEnvVar.String(), "arg1=", @@ -403,7 +512,7 @@ func Test_OptsFromBuildInfo(t *testing.T) { }, } - result := OptsFromBuildInfo(manifest.Name, tt.serviceName, manifest.Build[tt.serviceName], tt.initialOpts, &tt.mr, okCtx) + result := OptsFromBuildInfo(manifest, tt.serviceName, manifest.Build[tt.serviceName], tt.initialOpts, &tt.mr, okCtx) require.Equal(t, tt.expected, result) }) } diff --git a/pkg/cmd/build/file.go b/pkg/cmd/build/file.go index dddbb2796450..c6042b28e8c2 100644 --- a/pkg/cmd/build/file.go +++ b/pkg/cmd/build/file.go @@ -14,13 +14,11 @@ package build import ( - "bufio" "fmt" "os" "path/filepath" "strings" - "github.com/okteto/okteto/pkg/config" "github.com/okteto/okteto/pkg/constants" oktetoLog "github.com/okteto/okteto/pkg/log" "github.com/okteto/okteto/pkg/registry" @@ -32,8 +30,8 @@ const ( ) // GetDockerfile returns the dockerfile with the cache and registry translations -func GetDockerfile(dockerFile string, okCtx OktetoContextInterface) (string, error) { - file, err := getTranslatedDockerFile(dockerFile, okCtx) +func GetDockerfile(dockerFile string, okCtx OktetoContextInterface, repoURL, dockerfilePath, target string) (string, error) { + file, err := getTranslatedDockerFile(dockerFile, okCtx, repoURL, dockerFile, target) if err != nil { return "", errors.Wrap(err, "failed to create temporary build folder") } @@ -41,49 +39,21 @@ func GetDockerfile(dockerFile string, okCtx OktetoContextInterface) (string, err return file, nil } -func getTranslatedDockerFile(filename string, okCtx OktetoContextInterface) (string, error) { - file, err := os.Open(filename) +func getTranslatedDockerFile(filename string, okCtx OktetoContextInterface, repoURL, dockerfilePath, target string) (string, error) { + translator, err := newDockerfileTranslator(okCtx, repoURL, dockerfilePath, target) if err != nil { return "", err } - defer func() { - if err := file.Close(); err != nil { - oktetoLog.Debugf("Error closing file %s: %s", filename, err) - } - }() - - scanner := bufio.NewScanner(file) - - dockerfileTmpFolder := filepath.Join(config.GetOktetoHome(), ".dockerfile") - if err := os.MkdirAll(dockerfileTmpFolder, 0700); err != nil { - return "", fmt.Errorf("failed to create %s: %w", dockerfileTmpFolder, err) - } - - tmpFile, err := os.CreateTemp(dockerfileTmpFolder, "buildkit-") - if err != nil { - return "", err - } - - datawriter := bufio.NewWriter(tmpFile) - defer datawriter.Flush() - for scanner.Scan() { - line := scanner.Text() - translatedLine := translateOktetoRegistryImage(line, okCtx) - _, err = datawriter.WriteString(translatedLine + "\n") - if err != nil { - return "", fmt.Errorf("failed to write dockerfile: %w", err) - } - } - if err := scanner.Err(); err != nil { + if err := translator.translate(filename); err != nil { return "", err } - if err := copyDockerIgnore(filename, tmpFile.Name()); err != nil { + if err := copyDockerIgnore(filename, translator.tmpFileName); err != nil { return "", err } - return tmpFile.Name(), nil + return translator.tmpFileName, nil } func translateOktetoRegistryImage(input string, okCtx OktetoContextInterface) string { diff --git a/pkg/cmd/build/translator.go b/pkg/cmd/build/translator.go new file mode 100644 index 000000000000..f2c6272f1e0e --- /dev/null +++ b/pkg/cmd/build/translator.go @@ -0,0 +1,257 @@ +// Copyright 2025 The Okteto Authors +// 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 build + +import ( + "bufio" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/okteto/okteto/pkg/config" + "github.com/okteto/okteto/pkg/constants" + "github.com/okteto/okteto/pkg/registry" +) + +const ( + tmpFilePrefix = "buildkit-" +) + +var ( + cacheMountRegex = regexp.MustCompile(`^RUN.*--mount=.*type=cache`) + mountRegex = regexp.MustCompile(`--mount=([^[:space:]]+)`) + targetRegex = regexp.MustCompile(`target=([^,\s]+)`) + hasIDParamRegex = regexp.MustCompile(`\bid=.*`) +) + +type opener interface { + Open(file string) (io.ReadWriteCloser, error) +} + +type fileOpener struct{} + +func (fileOpener) Open(file string) (io.ReadWriteCloser, error) { + return os.OpenFile(file, os.O_RDWR, 0644) +} + +type tmpFileCreator interface { + Create(dir string) (string, error) +} +type osTmpFileCreator struct{} + +func (osTmpFileCreator) Create(dir string) (string, error) { + file, err := os.CreateTemp(dir, tmpFilePrefix) + if err != nil { + return "", err + } + if err := file.Close(); err != nil { + return "", err + } + return file.Name(), nil +} + +type DockerfileTranslator struct { + opener opener + tmpFileCreator tmpFileCreator + tmpFolder string + tmpFileName string + translators []translator +} + +func newDockerfileTranslator(okCtx OktetoContextInterface, repoURL, dockerfilePath, target string) (*DockerfileTranslator, error) { + dockerfileTmpFolder := filepath.Join(config.GetOktetoHome(), ".dockerfile") + if err := os.MkdirAll(dockerfileTmpFolder, 0700); err != nil { + return nil, fmt.Errorf("failed to create %s: %w", dockerfileTmpFolder, err) + } + + return &DockerfileTranslator{ + opener: fileOpener{}, + tmpFileCreator: osTmpFileCreator{}, + tmpFolder: dockerfileTmpFolder, + translators: []translator{ + newRegistryTranslator(okCtx), + newCacheMountTranslator(repoURL, dockerfilePath, target), + }, + }, nil +} + +func (dt *DockerfileTranslator) translate(filename string) error { + readerFile, err := dt.opener.Open(filename) + if err != nil { + return err + } + defer readerFile.Close() + + dt.tmpFileName, err = dt.tmpFileCreator.Create(dt.tmpFolder) + if err != nil { + return err + } + + writerFile, err := dt.opener.Open(dt.tmpFileName) + if err != nil { + return err + } + defer writerFile.Close() + + scanner := bufio.NewScanner(readerFile) + datawriter := bufio.NewWriter(writerFile) + defer datawriter.Flush() + + for scanner.Scan() { + line := scanner.Text() + + result := line + for _, translator := range dt.translators { + result = translator.translate(result) + } + + _, err := datawriter.WriteString(result + "\n") + if err != nil { + return fmt.Errorf("failed to write dockerfile: %w", err) + } + } + if err := scanner.Err(); err != nil { + return err + } + return nil +} + +type translator interface { + translate(line string) string +} + +type registryTranslator struct { + replacer registry.Replacer + userNs string + globalNamespace string +} + +func newRegistryTranslator(okCtx OktetoContextInterface) registryTranslator { + globalNamespace := constants.DefaultGlobalNamespace + ctxGlobalNamespace := okCtx.GetGlobalNamespace() + if ctxGlobalNamespace != "" { + globalNamespace = ctxGlobalNamespace + } + + return registryTranslator{ + replacer: registry.NewRegistryReplacer(GetRegistryConfigFromOktetoConfig(okCtx)), + userNs: okCtx.GetNamespace(), + globalNamespace: globalNamespace, + } +} + +func (rt registryTranslator) translate(line string) string { + if strings.Contains(line, constants.DevRegistry) { + result := rt.replacer.Replace(line, constants.DevRegistry, rt.userNs) + return result + } + + if strings.Contains(line, constants.GlobalRegistry) { + result := rt.replacer.Replace(line, constants.GlobalRegistry, rt.globalNamespace) + return result + } + return line +} + +type cacheMountTranslator struct { + repo string + dockerfilePath string + buildTarget string + hash func(repositoryURL, dockerfilePath, buildTarget, cacheTarget string) string +} + +func newCacheMountTranslator(repo, dockerfilePath, target string) cacheMountTranslator { + + return cacheMountTranslator{ + repo: repo, + dockerfilePath: dockerfilePath, + buildTarget: target, + hash: generateProjectHash, + } +} + +func generateProjectHash(repositoryURL, dockerfilePath, buildTarget, cacheTarget string) string { + // Create input string for hashing + input := fmt.Sprintf("build-%s-%s-%s-%s", repositoryURL, dockerfilePath, buildTarget, cacheTarget) + + // Generate SHA256 hash + hasher := sha256.New() + hasher.Write([]byte(input)) + hash := hasher.Sum(nil) + + // Return first 12 characters of hex hash for readability + return hex.EncodeToString(hash)[:12] +} + +func (cmt cacheMountTranslator) translate(line string) string { + + // Check if this RUN command has a cache mount + if !cacheMountRegex.MatchString(line) { + return line + } + + // Find all --mount= occurrences and process them individually + result := line + + // Find all mount definitions + matches := mountRegex.FindAllStringSubmatch(result, -1) + for _, match := range matches { + + // Check that the mountID has mount and params + // Example: --mount=id=test,type=cache,target=... + // [0] --mount=id=test,type=cache,target=... + // [1] id=test,type=cache,target=... + if len(match) < 2 { + continue + } + + fullMount := match[0] // --mount=type=cache,target=... + mountParams := match[1] // type=cache,target=... + + // Check if this is a cache mount + // Example: type=cache,target=... => true + // Example: type=secret => false + if !strings.Contains(mountParams, "type=cache") { + continue + } + + // Check if it already has an id + // Example: id=test,type=cache,target=... => true + // Example: type=cache,target=... => false + // Example: uid=101,gid=101,type=cache,target=... => false + if hasIDParamRegex.MatchString(mountParams) { + continue + } + + // Extract target from this specific mount + target := "" + if targetMatch := targetRegex.FindStringSubmatch(mountParams); len(targetMatch) > 1 { + target = targetMatch[1] + } + + // Generate unique ID for this mount + id := cmt.hash(cmt.repo, cmt.dockerfilePath, cmt.buildTarget, target) + + // Replace this specific mount with the one that includes the ID + newMount := fmt.Sprintf("--mount=id=%s,%s", id, mountParams) + result = strings.Replace(result, fullMount, newMount, 1) + } + + return result +} diff --git a/pkg/cmd/build/translator_test.go b/pkg/cmd/build/translator_test.go new file mode 100644 index 000000000000..e55c5cf21990 --- /dev/null +++ b/pkg/cmd/build/translator_test.go @@ -0,0 +1,742 @@ +// Copyright 2023 The Okteto Authors +// 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 build + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/okteto/okteto/pkg/constants" + "github.com/okteto/okteto/pkg/registry" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +// Mock implementations for testing +type mockOpener struct { + files map[string]*mockFile + shouldFail bool +} + +type mockFile struct { + content string + pos int +} + +func (mf *mockFile) Read(p []byte) (n int, err error) { + if mf.pos >= len(mf.content) { + return 0, io.EOF + } + n = copy(p, mf.content[mf.pos:]) + mf.pos += n + return n, nil +} + +func (mf *mockFile) Write(p []byte) (n int, err error) { + mf.content += string(p) + return len(p), nil +} + +func (mf *mockFile) Close() error { + return nil +} + +func newMockFile(content string) *mockFile { + return &mockFile{content: content, pos: 0} +} + +func (mo *mockOpener) Open(file string) (io.ReadWriteCloser, error) { + if mo.shouldFail { + return nil, fmt.Errorf("mock open error") + } + if f, exists := mo.files[file]; exists { + return f, nil + } + // Create a new empty file if it doesn't exist + mo.files[file] = newMockFile("") + return mo.files[file], nil +} + +type mockTmpFileCreator struct { + shouldFail bool + fileName string +} + +func (mtfc *mockTmpFileCreator) Create(dir string) (string, error) { + if mtfc.shouldFail { + return "", fmt.Errorf("mock tmp file creation error") + } + return mtfc.fileName, nil +} + +type mockOktetoContext struct { + namespace string + globalNamespace string + registryURL string +} + +func (moc *mockOktetoContext) GetCurrentName() string { return "test" } +func (moc *mockOktetoContext) GetNamespace() string { return moc.namespace } +func (moc *mockOktetoContext) GetGlobalNamespace() string { return moc.globalNamespace } +func (moc *mockOktetoContext) GetCurrentBuilder() string { return "test" } +func (moc *mockOktetoContext) GetCurrentCertStr() string { return "" } +func (moc *mockOktetoContext) GetCurrentCfg() *clientcmdapi.Config { return nil } +func (moc *mockOktetoContext) GetCurrentToken() string { return "" } +func (moc *mockOktetoContext) GetCurrentUser() string { return "" } +func (moc *mockOktetoContext) ExistsContext() bool { return true } +func (moc *mockOktetoContext) IsOktetoCluster() bool { return true } +func (moc *mockOktetoContext) IsInsecure() bool { return false } +func (moc *mockOktetoContext) UseContextByBuilder() {} +func (moc *mockOktetoContext) GetTokenByContextName(name string) (string, error) { return "", nil } +func (moc *mockOktetoContext) GetRegistryURL() string { return moc.registryURL } + +type mockReplacerConfig struct { + registryURL string +} + +func (mrc *mockReplacerConfig) GetRegistryURL() string { + return mrc.registryURL +} + +func TestNewDockerfileTranslator(t *testing.T) { + tests := []struct { + name string + okCtx OktetoContextInterface + repoURL string + dockerfilePath string + target string + expectedTranslatorCount int + }{ + { + name: "successful creation with default global namespace", + okCtx: &mockOktetoContext{ + namespace: "test-ns", + globalNamespace: "", + registryURL: "registry.example.com", + }, + repoURL: "https://github.com/test/repo", + dockerfilePath: "Dockerfile", + target: "prod", + expectedTranslatorCount: 2, + }, + { + name: "successful creation with custom global namespace", + okCtx: &mockOktetoContext{ + namespace: "user-ns", + globalNamespace: "custom-global", + registryURL: "registry.example.com", + }, + repoURL: "https://github.com/test/repo", + dockerfilePath: "Dockerfile", + target: "dev", + expectedTranslatorCount: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dt, err := newDockerfileTranslator(tt.okCtx, tt.repoURL, tt.dockerfilePath, tt.target) + + require.NoError(t, err) + require.NotNil(t, dt) + require.Len(t, dt.translators, tt.expectedTranslatorCount) + require.NotEmpty(t, dt.tmpFolder) + }) + } +} + +func TestDockerfileTranslator_Translate(t *testing.T) { + tests := []struct { + name string + expectedOutput string + setupTranslator func() *DockerfileTranslator + }{ + { + name: "simple dockerfile with no translations needed", + expectedOutput: "FROM ubuntu:20.04\nRUN echo hello\n", + setupTranslator: func() *DockerfileTranslator { + mockOpener := &mockOpener{ + files: make(map[string]*mockFile), + } + mockOpener.files["input.dockerfile"] = newMockFile("FROM ubuntu:20.04\nRUN echo hello") + + return &DockerfileTranslator{ + opener: mockOpener, + tmpFileCreator: &mockTmpFileCreator{fileName: "temp.dockerfile"}, + tmpFolder: "/tmp", + translators: []translator{}, // No translators + } + }, + }, + { + name: "dockerfile with dev registry translation", + expectedOutput: "FROM registry.example.com/test-ns/myimage:latest\n", + setupTranslator: func() *DockerfileTranslator { + mockOpener := &mockOpener{ + files: make(map[string]*mockFile), + } + mockOpener.files["input.dockerfile"] = newMockFile(fmt.Sprintf("FROM %s/myimage:latest", constants.DevRegistry)) + + registryTranslator := registryTranslator{ + replacer: registry.NewRegistryReplacer(&mockReplacerConfig{registryURL: "registry.example.com"}), + userNs: "test-ns", + globalNamespace: "okteto", + } + + return &DockerfileTranslator{ + opener: mockOpener, + tmpFileCreator: &mockTmpFileCreator{fileName: "temp.dockerfile"}, + tmpFolder: "/tmp", + translators: []translator{registryTranslator}, + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dt := tt.setupTranslator() + + err := dt.translate("input.dockerfile") + require.NoError(t, err) + + // Check the output content + outputFile := dt.opener.(*mockOpener).files[dt.tmpFileName] + require.NotNil(t, outputFile) + assert.Equal(t, tt.expectedOutput, outputFile.content) + }) + } +} + +type fakeTranslator struct { + called bool +} + +func (ft *fakeTranslator) translate(line string) string { + ft.called = true + return line +} + +func TestDockerfileTranslator_AllTranslatorsUsed(t *testing.T) { + t1 := &fakeTranslator{} + t2 := &fakeTranslator{} + t3 := &fakeTranslator{} + + mockOpener := &mockOpener{ + files: make(map[string]*mockFile), + } + mockOpener.files["input.dockerfile"] = newMockFile(fmt.Sprintf("FROM %s/myimage:latest", constants.DevRegistry)) + dt := &DockerfileTranslator{ + opener: mockOpener, + tmpFileCreator: &mockTmpFileCreator{fileName: "temp.dockerfile"}, + tmpFolder: "/tmp", + translators: []translator{t1, t2, t3}, + } + + err := dt.translate("input.dockerfile") + require.NoError(t, err) + + assert.True(t, t1.called) + assert.True(t, t2.called) + assert.True(t, t3.called) +} + +func TestDockerfileTranslator_Translate_Failures(t *testing.T) { + tests := []struct { + name string + setupTranslator func() *DockerfileTranslator + filename string + expectedError string + }{ + { + name: "opener fails to open input file", + setupTranslator: func() *DockerfileTranslator { + return &DockerfileTranslator{ + opener: &mockOpener{shouldFail: true}, + tmpFileCreator: &mockTmpFileCreator{fileName: "temp.dockerfile"}, + tmpFolder: "/tmp", + translators: []translator{}, + } + }, + filename: "nonexistent.dockerfile", + expectedError: "mock open error", + }, + { + name: "tmp file creator fails", + setupTranslator: func() *DockerfileTranslator { + mockOpener := &mockOpener{ + files: make(map[string]*mockFile), + } + mockOpener.files["input.dockerfile"] = newMockFile("FROM ubuntu:20.04") + + return &DockerfileTranslator{ + opener: mockOpener, + tmpFileCreator: &mockTmpFileCreator{shouldFail: true}, + tmpFolder: "/tmp", + translators: []translator{}, + } + }, + filename: "input.dockerfile", + expectedError: "mock tmp file creation error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dt := tt.setupTranslator() + + err := dt.translate(tt.filename) + require.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedError) + }) + } +} + +func TestRegistryTranslator_Translate(t *testing.T) { + rt := registryTranslator{ + replacer: registry.NewRegistryReplacer(&mockReplacerConfig{registryURL: "registry.example.com"}), + userNs: "test-ns", + globalNamespace: "global-ns", + } + + tests := []struct { + name string + input string + expected string + }{ + { + name: "translates dev registry", + input: fmt.Sprintf("FROM %s/myimage:latest", constants.DevRegistry), + expected: "FROM registry.example.com/test-ns/myimage:latest", + }, + { + name: "translates global registry", + input: fmt.Sprintf("FROM %s/shared:v1.0", constants.GlobalRegistry), + expected: "FROM registry.example.com/global-ns/shared:v1.0", + }, + { + name: "handles dev registry at start of line", + input: fmt.Sprintf("%s/builder:latest", constants.DevRegistry), + expected: "registry.example.com/test-ns/builder:latest", + }, + { + name: "handles dev registry with whitespace prefix", + input: fmt.Sprintf("COPY --from %s/builder:latest /app .", constants.DevRegistry), + expected: "COPY --from registry.example.com/test-ns/builder:latest /app .", + }, + { + name: "leaves non-okteto registries unchanged", + input: "FROM docker.io/ubuntu:20.04", + expected: "FROM docker.io/ubuntu:20.04", + }, + { + name: "leaves standard docker hub image unchanged", + input: "FROM ubuntu:20.04", + expected: "FROM ubuntu:20.04", + }, + { + name: "leaves private registry image unchanged", + input: "FROM private.registry.com/myapp:latest", + expected: "FROM private.registry.com/myapp:latest", + }, + { + name: "leaves empty line unchanged", + input: "", + expected: "", + }, + { + name: "leaves comment line unchanged", + input: "# This is a comment", + expected: "# This is a comment", + }, + { + name: "leaves run command without registry unchanged", + input: "RUN apt-get update", + expected: "RUN apt-get update", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := rt.translate(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestGenerateProjectHash(t *testing.T) { + tests := []struct { + name string + repositoryURL string + manifestName string + path string + target string + expectedHashLength int + comparisonInputs []string + shouldBeDifferent bool + }{ + { + name: "generates consistent hash for same inputs", + repositoryURL: "https://github.com/test/repo", + manifestName: "Dockerfile", + path: "production", + target: "production", + expectedHashLength: 12, + comparisonInputs: []string{"https://github.com/test/repo", "Dockerfile", "production", "production"}, + shouldBeDifferent: false, + }, + { + name: "different repos produce different hashes", + repositoryURL: "https://github.com/different/repo", + manifestName: "Dockerfile", + path: "production", + target: "production", + expectedHashLength: 12, + comparisonInputs: []string{"https://github.com/test/repo", "Dockerfile", "production", "production"}, + shouldBeDifferent: true, + }, + { + name: "different paths produce different hashes", + repositoryURL: "https://github.com/test/repo", + manifestName: "Dockerfile", + path: "development", + target: "development", + expectedHashLength: 12, + comparisonInputs: []string{"https://github.com/test/repo", "Dockerfile", "production", "production"}, + shouldBeDifferent: true, + }, + { + name: "handles empty values", + repositoryURL: "", + manifestName: "", + path: "", + target: "", + expectedHashLength: 12, + comparisonInputs: []string{"https://github.com/test/repo", "Dockerfile", "production", "production"}, + shouldBeDifferent: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hash := generateProjectHash(tt.repositoryURL, tt.manifestName, tt.path, tt.target) + + assert.Len(t, hash, tt.expectedHashLength) + assert.NotEmpty(t, hash) + + // Test consistency + hash2 := generateProjectHash(tt.repositoryURL, tt.manifestName, tt.path, tt.target) + assert.Equal(t, hash, hash2) + + // Test comparison + comparisonHash := generateProjectHash(tt.comparisonInputs[0], tt.comparisonInputs[1], tt.comparisonInputs[2], tt.comparisonInputs[3]) + assert.Equal(t, tt.shouldBeDifferent, hash != comparisonHash) + }) + } +} + +func TestCacheMountTranslator_Translate(t *testing.T) { + cmt := newCacheMountTranslator("https://github.com/test/repo", "Dockerfile", "prod") + // Override projectHash for predictable test results + cmt.hash = func(repositoryURL, manifestName, path, target string) string { + return fmt.Sprintf("%s-%s-%s-%s", repositoryURL, manifestName, path, target) + } + + tests := []struct { + name string + input string + expected string + }{ + { + name: "adds id to cache mount without target", + input: "RUN --mount=type=cache apt-get update", + expected: "RUN --mount=id=https://github.com/test/repo-Dockerfile-prod-,type=cache apt-get update", + }, + { + name: "adds id with target to cache mount", + input: "RUN --mount=type=cache,target=/root/.cache pip install -r requirements.txt", + expected: "RUN --mount=id=https://github.com/test/repo-Dockerfile-prod-/root/.cache,type=cache,target=/root/.cache pip install -r requirements.txt", + }, + { + name: "handles complex cache mount with multiple parameters", + input: "RUN --mount=type=cache,target=/var/cache/apt,sharing=locked apt-get update", + expected: "RUN --mount=id=https://github.com/test/repo-Dockerfile-prod-/var/cache/apt,type=cache,target=/var/cache/apt,sharing=locked apt-get update", + }, + { + name: "handles cache mount with quoted target", + input: `RUN --mount=type=cache,target="/go/pkg/mod" go mod download`, + expected: `RUN --mount=id=https://github.com/test/repo-Dockerfile-prod-"/go/pkg/mod",type=cache,target="/go/pkg/mod" go mod download`, + }, + { + name: "leaves non-RUN command unchanged", + input: "FROM ubuntu:20.04", + expected: "FROM ubuntu:20.04", + }, + { + name: "leaves RUN without cache mount unchanged", + input: "RUN apt-get update", + expected: "RUN apt-get update", + }, + { + name: "leaves cache mount with existing id unchanged", + input: "RUN --mount=type=cache,id=custom-id,target=/cache apt-get update", + expected: "RUN --mount=type=cache,id=custom-id,target=/cache apt-get update", + }, + { + name: "leaves RUN with different mount type unchanged", + input: "RUN --mount=type=bind,source=.,target=/app ls", + expected: "RUN --mount=type=bind,source=.,target=/app ls", + }, + { + name: "leaves empty line unchanged", + input: "", + expected: "", + }, + { + name: "leaves comment line unchanged", + input: "# Install dependencies", + expected: "# Install dependencies", + }, + { + name: "leaves RUN with secret mount unchanged", + input: "RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret", + expected: "RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret", + }, + { + name: "handles multiple cache mounts in single RUN command", + input: "RUN --mount=type=cache,target=./.eslintcache --mount=type=cache,target=./.yarn/cache,sharing=private --mount=type=cache,target=./node_modules,sharing=private yarn install --immutable", + expected: "RUN --mount=id=https://github.com/test/repo-Dockerfile-prod-./.eslintcache,type=cache,target=./.eslintcache --mount=id=https://github.com/test/repo-Dockerfile-prod-./.yarn/cache,type=cache,target=./.yarn/cache,sharing=private --mount=id=https://github.com/test/repo-Dockerfile-prod-./node_modules,type=cache,target=./node_modules,sharing=private yarn install --immutable", + }, + { + name: "handles multiple cache mounts in single RUN command, some with id", + input: "RUN --mount=id=test,type=cache,target=./.eslintcache --mount=type=cache,target=./.yarn/cache,sharing=private --mount=type=cache,target=./node_modules,sharing=private yarn install --immutable", + expected: "RUN --mount=id=test,type=cache,target=./.eslintcache --mount=id=https://github.com/test/repo-Dockerfile-prod-./.yarn/cache,type=cache,target=./.yarn/cache,sharing=private --mount=id=https://github.com/test/repo-Dockerfile-prod-./node_modules,type=cache,target=./node_modules,sharing=private yarn install --immutable", + }, + { + name: "handles one cache mount without id but with uid and gid", + input: "RUN --mount=type=cache,uid=1000,gid=1000,target=./.eslintcache --mount=type=cache,target=./.yarn/cache,sharing=private --mount=type=cache,target=./node_modules,sharing=private yarn install --immutable", + expected: "RUN --mount=id=https://github.com/test/repo-Dockerfile-prod-./.eslintcache,type=cache,uid=1000,gid=1000,target=./.eslintcache --mount=id=https://github.com/test/repo-Dockerfile-prod-./.yarn/cache,type=cache,target=./.yarn/cache,sharing=private --mount=id=https://github.com/test/repo-Dockerfile-prod-./node_modules,type=cache,target=./node_modules,sharing=private yarn install --immutable", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := cmt.translate(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestOsTmpFileCreator_Create(t *testing.T) { + tests := []struct { + name string + setupTmpDir func(*testing.T) string + }{ + { + name: "creates temp file with correct prefix", + setupTmpDir: func(t *testing.T) string { + return t.TempDir() + }, + }, + { + name: "creates temp file in nested directory", + setupTmpDir: func(t *testing.T) string { + baseDir := t.TempDir() + nestedDir := filepath.Join(baseDir, "nested") + require.NoError(t, os.MkdirAll(nestedDir, 0755)) + return nestedDir + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + creator := osTmpFileCreator{} + tmpDir := tt.setupTmpDir(t) + + filename, err := creator.Create(tmpDir) + require.NoError(t, err) + + // Verify file was created with correct prefix and location + assert.True(t, strings.HasPrefix(filepath.Base(filename), tmpFilePrefix)) + assert.True(t, strings.HasPrefix(filename, tmpDir)) + + // Verify file exists and can be written to + file, err := os.OpenFile(filename, os.O_RDWR, 0644) + require.NoError(t, err) + defer file.Close() + + _, err = file.WriteString("test content") + require.NoError(t, err) + }) + } +} + +func TestFileOpener_Open(t *testing.T) { + tests := []struct { + name string + content string + setupFile func(*testing.T, string) string + }{ + { + name: "opens existing file with content", + content: "test content", + setupFile: func(t *testing.T, content string) string { + tmpFile, err := os.CreateTemp(t.TempDir(), "test-") + require.NoError(t, err) + + _, err = tmpFile.WriteString(content) + require.NoError(t, err) + require.NoError(t, tmpFile.Close()) + + return tmpFile.Name() + }, + }, + { + name: "opens empty file", + content: "", + setupFile: func(t *testing.T, content string) string { + tmpFile, err := os.CreateTemp(t.TempDir(), "empty-") + require.NoError(t, err) + require.NoError(t, tmpFile.Close()) + + return tmpFile.Name() + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opener := fileOpener{} + filename := tt.setupFile(t, tt.content) + defer os.Remove(filename) + + file, err := opener.Open(filename) + require.NoError(t, err) + defer file.Close() + + // Verify we can read from it + content, err := io.ReadAll(file) + require.NoError(t, err) + assert.Equal(t, tt.content, string(content)) + + // Verify we can close it + err = file.Close() + require.NoError(t, err) + }) + } +} + +func TestFileOpener_Open_Failures(t *testing.T) { + tests := []struct { + name string + filePath string + }{ + { + name: "nonexistent file returns error", + filePath: "/nonexistent/file/path", + }, + { + name: "invalid file path returns error", + filePath: "/invalid\x00path/file", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opener := fileOpener{} + + _, err := opener.Open(tt.filePath) + require.Error(t, err) + }) + } +} + +func TestDockerfileTranslator_Integration(t *testing.T) { + tests := []struct { + name string + dockerfileContent string + okCtx OktetoContextInterface + repoURL string + target string + expectedContains []string + expectedCacheMountID bool + }{ + { + name: "simple dockerfile translation", + dockerfileContent: `FROM ubuntu:20.04 +RUN --mount=type=cache,target=/var/cache/apt apt-get update +FROM alpine:latest`, + okCtx: &mockOktetoContext{ + namespace: "user-ns", + globalNamespace: "global-ns", + registryURL: "registry.example.com", + }, + repoURL: "https://github.com/test/repo", + target: "prod", + expectedContains: []string{ + "FROM ubuntu:20.04", + "FROM alpine:latest", + }, + expectedCacheMountID: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + dockerfilePath := filepath.Join(tmpDir, "Dockerfile") + + err := os.WriteFile(dockerfilePath, []byte(tt.dockerfileContent), 0644) + require.NoError(t, err) + + inputContent, err := os.ReadFile(dockerfilePath) + require.NoError(t, err) + require.NotEmpty(t, string(inputContent)) + t.Logf("Input Dockerfile content: %q", string(inputContent)) + + dt, err := newDockerfileTranslator(tt.okCtx, tt.repoURL, dockerfilePath, tt.target) + require.NoError(t, err) + + err = dt.translate(dockerfilePath) + require.NoError(t, err) + + require.NotEmpty(t, dt.tmpFileName, "temporary file name should be set after translation") + t.Logf("Temporary file created: %s", dt.tmpFileName) + + _, err = os.Stat(dt.tmpFileName) + require.NoError(t, err, "temporary file should exist") + + translatedContent, err := os.ReadFile(dt.tmpFileName) + require.NoError(t, err) + defer os.Remove(dt.tmpFileName) + + translatedStr := string(translatedContent) + t.Logf("Translated content: %q", translatedStr) + + assert.NotEmpty(t, translatedStr, "translated content should not be empty") + + for _, expected := range tt.expectedContains { + assert.Contains(t, translatedStr, expected, "should contain: %s", expected) + } + + assert.Equal(t, tt.expectedCacheMountID, strings.Contains(translatedStr, "--mount=id="), "cache mount ID should be present") + }) + } +} diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index c762a07ba9e5..3cbc5da7a328 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -717,7 +717,7 @@ func formatEnvVarValueForDocker(value string) string { // generateCacheID creates a unique cache identifier based on repository, manifest name, and test name func generateCacheID(repositoryURL, manifestName, testName, path string) string { // Create input string for hashing - input := fmt.Sprintf("%s-%s-%s-%s", repositoryURL, manifestName, testName, path) + input := fmt.Sprintf("test-%s-%s-%s-%s", repositoryURL, manifestName, testName, path) // Generate SHA256 hash hasher := sha256.New() From 50cabeb92c1bae736e5b7b98f135900623857fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Mon, 6 Oct 2025 17:02:33 +0200 Subject: [PATCH 08/21] refactor: rename release-external job to release-brew-formula and add release-gh-actions job (#4793) - Updated the CircleCI configuration to rename the 'release-external' job to 'release-brew-formula'. - Introduced a new job 'release-gh-actions' to handle GitHub Actions releases, including SSH key management and artifact attachment. - Adjusted workflow to include the new 'release-gh-actions' job with appropriate filters for tag releases. Signed-off-by: Javier Lopez --- .circleci/config.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fbb01761016a..0dacb4f18a40 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -484,7 +484,7 @@ jobs: - run: *init-gcloud - run: 'gsutil -m -h "Cache-Control: no-store" -h "Content-Type: text/x-sh" cp ./scripts/get-okteto.sh gs://get.okteto.com/get-okteto.sh' - release-external: + release-brew-formula: executor: golang-ci steps: - checkout @@ -503,6 +503,16 @@ jobs: ./scripts/update_homebrew_formula.sh $CIRCLE_TAG $sha $sha_arm pushd homebrew-cli git push git@github.com:okteto/homebrew-cli.git master + + release-gh-actions: + executor: golang-ci + steps: + - checkout + - run: *init-gcloud + - add_ssh_keys: + fingerprints: + # This key belongs to oktetobot user in GitHub + - SHA256:+wgdECJJEEyF/iyl8Y8EV/tpOEQIv6zZ/0LRqRChS18 - run: name: Auto-update-actions command: ./scripts/ci/release-github-actions.sh $CIRCLE_TAG @@ -785,7 +795,16 @@ workflows: tags: only: - *release-regex - - release-external: + - release-gh-actions: + context: GKE + requires: + - release-job + filters: + branches: + ignore: /.*/ + tags: + only: /^\d+\.\d+\.\d+$/ + - release-brew-formula: context: GKE requires: - release-job From 79a35cc7c65c2d241dcdc2fce44b8746903befbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:02:36 +0200 Subject: [PATCH 09/21] build(deps): bump rack from 3.1.16 to 3.1.17 in /samples/ruby (#4795) Bumps [rack](https://github.com/rack/rack) from 3.1.16 to 3.1.17. - [Release notes](https://github.com/rack/rack/releases) - [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md) - [Commits](https://github.com/rack/rack/compare/v3.1.16...v3.1.17) --- updated-dependencies: - dependency-name: rack dependency-version: 3.1.17 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/ruby/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ruby/Gemfile.lock b/samples/ruby/Gemfile.lock index 6353e967fccc..3c1ea60e5616 100644 --- a/samples/ruby/Gemfile.lock +++ b/samples/ruby/Gemfile.lock @@ -6,7 +6,7 @@ GEM multi_json (1.15.0) mustermann (3.0.3) ruby2_keywords (~> 0.0.1) - rack (3.1.16) + rack (3.1.17) rack-protection (4.1.0) base64 (>= 0.1.0) logger (>= 1.6.0) From eac63bc50ff653d23d8c11cdb0bbfe00f28a3910 Mon Sep 17 00:00:00 2001 From: Ignacio Fuertes Date: Thu, 9 Oct 2025 12:36:45 +0200 Subject: [PATCH 10/21] Remove pipeline from error message indicating files not found (#4797) Signed-off-by: Nacho Fuertes --- pkg/errors/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 5bfc19fdb5d6..dfacfa7d23f8 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -192,7 +192,7 @@ var ( ErrNotManifestContentDetected = errors.New("couldn't detect okteto manifest content") // ErrCouldNotInferAnyManifest is raised when we can't detect any manifest to load - ErrCouldNotInferAnyManifest = errors.New("couldn't detect any manifest (okteto manifest, pipeline or compose)") + ErrCouldNotInferAnyManifest = errors.New("couldn't detect any manifest (okteto manifest or compose)") // ErrX509Hint should be included within a UserError.Hint when IsX509() return true ErrX509Hint = "Add the flag '--insecure-skip-tls-verify' to skip certificate verification.\n Follow this link to know more about configuring your own certificates with Okteto:\n https://www.okteto.com/docs/self-hosted/install/certificates/" From 5cfe045d2df035b92790a5df847ca61477722f0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:28:22 +0200 Subject: [PATCH 11/21] build(deps): bump rack from 3.1.17 to 3.1.18 in /samples/ruby (#4798) Bumps [rack](https://github.com/rack/rack) from 3.1.17 to 3.1.18. - [Release notes](https://github.com/rack/rack/releases) - [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md) - [Commits](https://github.com/rack/rack/compare/v3.1.17...v3.1.18) --- updated-dependencies: - dependency-name: rack dependency-version: 3.1.18 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/ruby/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ruby/Gemfile.lock b/samples/ruby/Gemfile.lock index 3c1ea60e5616..fdefc6050c1e 100644 --- a/samples/ruby/Gemfile.lock +++ b/samples/ruby/Gemfile.lock @@ -6,7 +6,7 @@ GEM multi_json (1.15.0) mustermann (3.0.3) ruby2_keywords (~> 0.0.1) - rack (3.1.17) + rack (3.1.18) rack-protection (4.1.0) base64 (>= 0.1.0) logger (>= 1.6.0) From 0f8f68fc672c07458c6c48c1eff9b4e5c128e7dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:29:00 +0200 Subject: [PATCH 12/21] build(deps): bump github.com/nwaples/rardecode/v2 (#4800) Bumps [github.com/nwaples/rardecode/v2](https://github.com/nwaples/rardecode) from 2.0.0-beta.4.0.20241112120701-034e449c6e78 to 2.2.0. - [Commits](https://github.com/nwaples/rardecode/commits/v2.2.0) --- updated-dependencies: - dependency-name: github.com/nwaples/rardecode/v2 dependency-version: 2.2.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f9a08542114c..9679760cd354 100644 --- a/go.mod +++ b/go.mod @@ -202,7 +202,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 // indirect + github.com/nwaples/rardecode/v2 v2.2.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/sorairolake/lzip-go v0.3.5 // indirect github.com/therootcompany/xz v1.0.1 // indirect diff --git a/go.sum b/go.sum index 55333b91297a..4a502241a447 100644 --- a/go.sum +++ b/go.sum @@ -1156,8 +1156,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 h1:MYzLheyVx1tJVDqfu3YnN4jtnyALNzLvwl+f58TcvQY= -github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= +github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A= +github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/okteto/buildkit v0.0.0-20250702110947-403f68808de1 h1:QHmolwLhs/XYEd0ANtDultZOZfo3MZ+U5StIc/PTMv8= github.com/okteto/buildkit v0.0.0-20250702110947-403f68808de1/go.mod h1:2dy9MoMtAHSNikt6f3r9nD97yk+DVd1gjiYF5gvf/Lc= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= From 886a7403b4bba5c3fb3f497f4ba477721f515eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Tue, 14 Oct 2025 10:15:19 +0200 Subject: [PATCH 13/21] refactor: enhance namespace destruction logic in localDestroyAllCommand (#4794) * refactor: enhance namespace destruction logic in localDestroyAllCommand - Introduced a new constant for the ticker duration during the destruction process. - Improved the waitForNamespaceDestroyAllToComplete method to ensure comprehensive checks for resource destruction. - Added a new checkAllResourcesDestroyed method to verify that all resources, including configmaps, are properly removed after the destruction operation. This refactor aims to enhance the reliability and clarity of the namespace destruction process. Signed-off-by: Javier Lopez * fix: improve logging for resource destruction checks in localDestroyAllCommand - Updated log message in waitForNamespaceDestroyAllToComplete method to clarify the error context when checking for destroyed resources. This change enhances the clarity of log outputs during the namespace destruction process. Signed-off-by: Javier Lopez * fix: scenario Signed-off-by: Javier Lopez * fix: correct typo in error message for resource destruction checks - Updated the error message in checkAllResourcesDestroyed method to fix the typo "where" to "were" for improved clarity. Signed-off-by: Javier Lopez --------- Signed-off-by: Javier Lopez --- cmd/destroy/all.go | 70 +++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/cmd/destroy/all.go b/cmd/destroy/all.go index 2ee81e83ba82..a3a07efad8d7 100644 --- a/cmd/destroy/all.go +++ b/cmd/destroy/all.go @@ -28,6 +28,11 @@ import ( "github.com/okteto/okteto/pkg/model" "github.com/okteto/okteto/pkg/okteto" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + destroyingAllTickerDuration = 30 * time.Second ) type localDestroyAllCommand struct { @@ -99,16 +104,21 @@ func (lda *localDestroyAllCommand) destroy(ctx context.Context, opts *Options) e } } +// waitForNamespaceDestroyAllToComplete waits for the namespace destroy all operation to complete. +// When the namespace status is Active, it performs comprehensive checks to verify all resources +// have been properly destroyed, including helm releases and dev environments that need explicit destruction. func (lda *localDestroyAllCommand) waitForNamespaceDestroyAllToComplete(ctx context.Context, namespace string) error { timeout := 5 * time.Minute ticker := time.NewTicker(1 * time.Second) to := time.NewTicker(timeout) + destroyingAllTicker := time.Now() c, _, err := lda.k8sClientProvider.Provide(okteto.GetContext().Cfg) if err != nil { return err } hasBeenDestroyingAll := false + jobNotFoundAfterXSeconds := false for { select { @@ -129,32 +139,20 @@ func (lda *localDestroyAllCommand) waitForNamespaceDestroyAllToComplete(ctx cont switch status { case "Active": - if hasBeenDestroyingAll { - // when status is active again check if all resources have been correctly destroyed - // list configmaps that belong okteto deployments - resourcesLabels := map[string]bool{ - model.GitDeployLabel: true, - model.StackLabel: true, - "dev.okteto.com/app": true, - } + // If we haven't been in DestroyingAll state for at least destroyingAllTickerDuration, wait before checking resources. + if !hasBeenDestroyingAll && time.Since(destroyingAllTicker) < destroyingAllTickerDuration { + jobNotFoundAfterXSeconds = true + } - cfgList, err := c.CoreV1().ConfigMaps(namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - return err + if err := lda.checkAllResourcesDestroyed(ctx, namespace, c); err != nil { + if jobNotFoundAfterXSeconds { + continue } - - // no configmap for resources of the given namespace should exist - for _, cfg := range cfgList.Items { - for l := range cfg.GetLabels() { - if _, ok := resourcesLabels[l]; ok { - return fmt.Errorf("namespace destroy all failed: some resources where not destroyed") - } - } + if hasBeenDestroyingAll { + return err } - - // exit the waiting loop when status is active again - return nil } + return nil case "DestroyingAll": // initial state would be active, check if this changes to assure namespace has been in destroying all status hasBeenDestroyingAll = true @@ -164,3 +162,31 @@ func (lda *localDestroyAllCommand) waitForNamespaceDestroyAllToComplete(ctx cont } } } + +// checkAllResourcesDestroyed performs a comprehensive check to verify all resources have been destroyed +func (lda *localDestroyAllCommand) checkAllResourcesDestroyed(ctx context.Context, namespace string, k8s kubernetes.Interface) error { + // when status is active again check if all resources have been correctly destroyed + // list configmaps that belong okteto deployments + resourcesLabels := map[string]bool{ + model.GitDeployLabel: true, + model.StackLabel: true, + "dev.okteto.com/app": true, + } + + cfgList, err := k8s.CoreV1().ConfigMaps(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + + // no configmap for resources of the given namespace should exist + for _, cfg := range cfgList.Items { + for l := range cfg.GetLabels() { + if _, ok := resourcesLabels[l]; ok { + return fmt.Errorf("some resources were not destroyed") + } + } + } + + // exit the waiting loop when status is active again + return nil +} From 915f5c5d1011cd0f2153c3305837354314cd3620 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:12:35 +0200 Subject: [PATCH 14/21] build(deps): bump sinatra from 4.1.0 to 4.2.0 in /samples/ruby (#4799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [sinatra](https://github.com/sinatra/sinatra) from 4.1.0 to 4.2.0. - [Changelog](https://github.com/sinatra/sinatra/blob/main/CHANGELOG.md) - [Commits](https://github.com/sinatra/sinatra/compare/v4.1.0...v4.2.0) --- updated-dependencies: - dependency-name: sinatra dependency-version: 4.2.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Javier López Barba --- samples/ruby/Gemfile | 2 +- samples/ruby/Gemfile.lock | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/samples/ruby/Gemfile b/samples/ruby/Gemfile index 25fcf8a89cd2..2661756aead7 100644 --- a/samples/ruby/Gemfile +++ b/samples/ruby/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'sinatra', '~> 4.1' +gem 'sinatra', '~> 4.2' gem 'sinatra-contrib', '~> 4.1' group :development do diff --git a/samples/ruby/Gemfile.lock b/samples/ruby/Gemfile.lock index fdefc6050c1e..3abfe001bfbe 100644 --- a/samples/ruby/Gemfile.lock +++ b/samples/ruby/Gemfile.lock @@ -1,13 +1,13 @@ GEM remote: https://rubygems.org/ specs: - base64 (0.2.0) - logger (1.6.1) + base64 (0.3.0) + logger (1.7.0) multi_json (1.15.0) - mustermann (3.0.3) + mustermann (3.0.4) ruby2_keywords (~> 0.0.1) - rack (3.1.18) - rack-protection (4.1.0) + rack (3.2.3) + rack-protection (4.2.0) base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) @@ -18,27 +18,27 @@ GEM ruby-debug-ide (0.7.0) rake (>= 0.8.1) ruby2_keywords (0.0.5) - sinatra (4.1.0) + sinatra (4.2.0) logger (>= 1.6.0) mustermann (~> 3.0) rack (>= 3.0.0, < 4) - rack-protection (= 4.1.0) + rack-protection (= 4.2.0) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - sinatra-contrib (4.1.0) + sinatra-contrib (4.2.0) multi_json (>= 0.0.2) mustermann (~> 3.0) - rack-protection (= 4.1.0) - sinatra (= 4.1.0) + rack-protection (= 4.2.0) + sinatra (= 4.2.0) tilt (~> 2.0) - tilt (2.4.0) + tilt (2.6.1) PLATFORMS ruby DEPENDENCIES ruby-debug-ide (~> 0.7.0) - sinatra (~> 4.1) + sinatra (~> 4.2) sinatra-contrib (~> 4.1) BUNDLED WITH From aead429d216fc3300453fc511c47d102249d66a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Wed, 22 Oct 2025 16:50:38 +0200 Subject: [PATCH 15/21] fix: improve error handling in log streaming during resource destruction (#4801) * fix: improve error handling in log streaming during resource destruction - Enhanced the error handling in the destroy method of localDestroyAllCommand to avoid logging warnings for context cancellation and deadline exceeded errors. This change ensures that only relevant connectivity issues are reported, improving the clarity of log outputs during the destruction process. Signed-off-by: Javier Lopez * fix: improve context cancellation handling and error messaging in namespace and preview operations - Added context cancellation handling for log streaming in the destroy and watch methods across multiple commands, ensuring that logs are properly canceled on interrupt. - Enhanced error messages in namespace deletion to provide clearer context when watching for deletion errors. - Improved logging during pipeline deployment to indicate progress and errors more effectively. These changes aim to enhance the reliability and clarity of operations involving namespace and preview management. Signed-off-by: Javier Lopez * fix: tests Signed-off-by: Javier Lopez * fix: enhance context cancellation handling in namespace destruction - Updated the destroy method in localDestroyAllCommand to improve context cancellation handling. The method now checks for context cancellation before sending completion signals to the exit channel, preventing unnecessary operations when the context is done. This change aims to enhance the reliability of the namespace destruction process by ensuring that operations respect context cancellations. Signed-off-by: Javier Lopez * address comments Signed-off-by: Javier Lopez --------- Signed-off-by: Javier Lopez --- cmd/destroy/all.go | 13 ++++++++++--- cmd/namespace/delete.go | 5 +++-- cmd/pipeline/deploy.go | 5 +++++ cmd/preview/deploy.go | 4 ++++ cmd/preview/destroy.go | 2 +- pkg/stream/stream.go | 13 +++++++------ 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/cmd/destroy/all.go b/cmd/destroy/all.go index a3a07efad8d7..5904899aef13 100644 --- a/cmd/destroy/all.go +++ b/cmd/destroy/all.go @@ -77,8 +77,11 @@ func (lda *localDestroyAllCommand) destroy(ctx context.Context, opts *Options) e wg.Add(1) go func(wg *sync.WaitGroup) { defer wg.Done() - exit <- lda.waitForNamespaceDestroyAllToComplete(waitCtx, opts.Namespace) - logsCtxCancel() + select { + case exit <- lda.waitForNamespaceDestroyAllToComplete(waitCtx, opts.Namespace): + case <-waitCtx.Done(): + // Context was cancelled, don't send to exit channel + } }(&wg) wg.Add(1) @@ -86,7 +89,10 @@ func (lda *localDestroyAllCommand) destroy(ctx context.Context, opts *Options) e defer wg.Done() connectionTimeout := 5 * time.Minute err := lda.oktetoClient.Stream().DestroyAllLogs(logsCtx, opts.Namespace, connectionTimeout) - if err != nil { + + // Check if error is not canceled because in the case of a timeout waiting the operation to complete, + // we cancel the context to stop streaming logs, but we should not display the warning + if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { oktetoLog.Warning("destroy all logs cannot be streamed due to connectivity issues") oktetoLog.Infof("destroy all logs cannot be streamed due to connectivity issues: %v", err) } @@ -95,6 +101,7 @@ func (lda *localDestroyAllCommand) destroy(ctx context.Context, opts *Options) e select { case <-stop: ctxCancel() + logsCtxCancel() oktetoLog.Infof("CTRL+C received, exit") return oktetoErrors.ErrIntSig case err := <-exit: diff --git a/cmd/namespace/delete.go b/cmd/namespace/delete.go index 75fb5bf455cc..ab8a99e719fc 100644 --- a/cmd/namespace/delete.go +++ b/cmd/namespace/delete.go @@ -81,7 +81,7 @@ func (nc *Command) ExecuteDeleteNamespace(ctx context.Context, namespace string, } if err := nc.watchDelete(ctx, namespace, k8sLogger); err != nil { - return err + return fmt.Errorf("watching namespace deletion stopped: %w", err) } oktetoLog.Success("Namespace '%s' deleted", namespace) @@ -121,7 +121,6 @@ func (nc *Command) watchDelete(ctx context.Context, namespace string, k8sLogger go func(wg *sync.WaitGroup) { defer wg.Done() exit <- nc.waitForNamespaceDeleted(waitCtx, namespace, k8sLogger) - logsCtxCancel() }(&wg) wg.Add(1) @@ -138,7 +137,9 @@ func (nc *Command) watchDelete(ctx context.Context, namespace string, k8sLogger select { case <-stop: ctxCancel() + logsCtxCancel() oktetoLog.Infof("CTRL+C received, exit") + oktetoLog.Information("CTRL+C received, cancelling wait and logs streaming but operation will continue in background") return oktetoErrors.ErrIntSig case err := <-exit: // wait until streaming logs have finished diff --git a/cmd/pipeline/deploy.go b/cmd/pipeline/deploy.go index 9dac42b55dcd..77d07b4fb410 100644 --- a/cmd/pipeline/deploy.go +++ b/cmd/pipeline/deploy.go @@ -365,6 +365,11 @@ func (pc *Command) waitUntilRunning(ctx context.Context, name, namespace string, wg.Add(1) go func(wg *sync.WaitGroup) { defer wg.Done() + if err := pc.okClient.Pipeline().WaitForActionProgressing(waitCtx, name, namespace, action.Name, timeout); err != nil { + oktetoLog.Infof("waiting for action to progress failed: %v", err) + exit <- err + return + } err := pc.streamPipelineLogs(waitCtx, name, namespace, action.Name, timeout) if err != nil { oktetoLog.Warning("pipeline logs cannot be streamed due to connectivity issues") diff --git a/cmd/preview/deploy.go b/cmd/preview/deploy.go index 01a9783ac043..ab920903f908 100644 --- a/cmd/preview/deploy.go +++ b/cmd/preview/deploy.go @@ -162,6 +162,10 @@ func (pw *Command) waitUntilRunning(ctx context.Context, name, namespace string, wg.Add(1) go func() { defer wg.Done() + if err := pw.okClient.Pipeline().WaitForActionProgressing(logsCtx, name, namespace, a.Name, timeout); err != nil { + oktetoLog.Infof("waiting for action to progress failed: %v", err) + return + } err := pw.okClient.Stream().PipelineLogs(logsCtx, name, namespace, a.Name, timeout) if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { oktetoLog.Warning("preview logs cannot be streamed due to connectivity issues") diff --git a/cmd/preview/destroy.go b/cmd/preview/destroy.go index 680413e5df19..9905554f12f8 100644 --- a/cmd/preview/destroy.go +++ b/cmd/preview/destroy.go @@ -142,7 +142,6 @@ func (c destroyPreviewCommand) watchDestroy(ctx context.Context, preview string, go func(wg *sync.WaitGroup) { defer wg.Done() exit <- c.waitForPreviewDestroyed(waitCtx, preview, timeout) - logsCtxCancel() }(&wg) wg.Add(1) @@ -160,6 +159,7 @@ func (c destroyPreviewCommand) watchDestroy(ctx context.Context, preview string, select { case <-stop: ctxCancel() + logsCtxCancel() oktetoLog.Infof("CTRL+C received, exit") return oktetoErrors.ErrIntSig case err := <-exit: diff --git a/pkg/stream/stream.go b/pkg/stream/stream.go index aeb780359d2b..ca0634f2e5b3 100644 --- a/pkg/stream/stream.go +++ b/pkg/stream/stream.go @@ -57,6 +57,9 @@ func requestWithRetry(ctx context.Context, c *http.Client, url string, timeout t // Attempt to stream logs resp, err := request(c, url) if err != nil { + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return nil, err + } continue } if resp.StatusCode == http.StatusOK { @@ -66,18 +69,16 @@ func requestWithRetry(ctx context.Context, c *http.Client, url string, timeout t if resp.StatusCode != http.StatusInternalServerError { return nil, fmt.Errorf("response from request: %s", resp.Status) } - // Check if context was cancelled or timed out - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return nil, err - } - // Calculate exponential backoff delay attempts++ retryDelay := calculateExponentialBackoff(attempts) + // Create a proper error message for logging + streamError := fmt.Errorf("received status %d, retrying", resp.StatusCode) + // Log the retry attempt oktetoLog.Warning("Unable to connect to stream logs, waiting to reconnect...") - oktetoLog.Infof("logs streaming error: %v, retrying in %v (attempt %d)", err, retryDelay, attempts) + oktetoLog.Infof("logs streaming error: %v, retrying in %v (attempt %d)", streamError, retryDelay, attempts) // Wait before retrying, but respect context cancellation select { From a75fb3acec98f1825ef2e37029febc36027722af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Fri, 24 Oct 2025 11:09:26 +0200 Subject: [PATCH 16/21] fix: improve analytics handling in development container activation (#4804) * fix: improve error handling in development container activation - Enhanced error handling in the devMode function to ensure that errors from waiting for the development container to run are properly returned. - Updated the newAutoDown function to include UpMetricsMetadata, allowing for better tracking of the auto-down feature's state. - Added isAutoDownEnabled field and corresponding method in UpMetricsMetadata to track the status of the auto-down feature. These changes aim to improve the reliability and clarity of the development container activation process and analytics tracking. Signed-off-by: Javier Lopez * feat: add isAutoDownEnabled to UpMetricsMetadata - Introduced the isAutoDownEnabled field in the UpMetricsMetadata struct to track the status of the auto-down feature. - Updated the toProps method to include this new field in the returned properties. These changes enhance the analytics capabilities related to the auto-down feature. Signed-off-by: Javier Lopez * fix: unit test Signed-off-by: Javier Lopez --------- Signed-off-by: Javier Lopez --- cmd/up/activate.go | 4 +++- cmd/up/autodown.go | 5 ++++- cmd/up/autodown_test.go | 2 +- cmd/up/up.go | 2 +- pkg/analytics/up.go | 6 ++++++ pkg/analytics/up_test.go | 5 +++++ 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/cmd/up/activate.go b/cmd/up/activate.go index 471e09ff6f74..5cc20e4dfbca 100644 --- a/cmd/up/activate.go +++ b/cmd/up/activate.go @@ -252,8 +252,10 @@ func (up *upContext) devMode(ctx context.Context, app apps.App, create bool) err if err := up.createDevContainer(ctx, app, create); err != nil { return err } + + err := up.waitUntilDevelopmentContainerIsRunning(ctx, app) up.analyticsMeta.DevContainerCreation(time.Since(startCreateDev)) - return up.waitUntilDevelopmentContainerIsRunning(ctx, app) + return err } func (up *upContext) createDevContainer(ctx context.Context, app apps.App, create bool) error { diff --git a/cmd/up/autodown.go b/cmd/up/autodown.go index 9b575f47c68e..95494b051878 100644 --- a/cmd/up/autodown.go +++ b/cmd/up/autodown.go @@ -17,6 +17,7 @@ import ( "context" "github.com/okteto/okteto/cmd/utils" + "github.com/okteto/okteto/pkg/analytics" "github.com/okteto/okteto/pkg/cmd/down" "github.com/okteto/okteto/pkg/env" oktetoErrors "github.com/okteto/okteto/pkg/errors" @@ -50,8 +51,10 @@ type downCmdRunner interface { } // newAutoDown creates a new AutoDown instance -func newAutoDown(ioCtrl *io.Controller, k8sLogger *io.K8sLogger, at analyticsTrackerInterface) *autoDownRunner { +func newAutoDown(ioCtrl *io.Controller, k8sLogger *io.K8sLogger, at analyticsTrackerInterface, upMeta *analytics.UpMetricsMetadata) *autoDownRunner { enabled := env.LoadBooleanOrDefault(autoDownEnvVar, false) + upMeta.IsAutoDownEnabled(enabled) + downCmd := down.New(afero.NewOsFs(), okteto.NewK8sClientProviderWithLogger(k8sLogger), at) return &autoDownRunner{ autoDown: enabled, diff --git a/cmd/up/autodown_test.go b/cmd/up/autodown_test.go index 7f3ddeda9240..fa4cc7b3fb80 100644 --- a/cmd/up/autodown_test.go +++ b/cmd/up/autodown_test.go @@ -97,7 +97,7 @@ func TestNewAutoDown(t *testing.T) { k8sLogger := io.NewK8sLogger() at := &mockAnalyticsTracker{} - ad := newAutoDown(ioCtrl, k8sLogger, at) + ad := newAutoDown(ioCtrl, k8sLogger, at, analytics.NewUpMetricsMetadata()) assert.Equal(t, tt.expectedResult, ad.autoDown) assert.NotNil(t, ad.ioCtrl) diff --git a/cmd/up/up.go b/cmd/up/up.go index 3a145e242a1d..59e39abbece7 100644 --- a/cmd/up/up.go +++ b/cmd/up/up.go @@ -237,7 +237,7 @@ okteto up api -- echo this is a test K8sClientProvider: okteto.NewK8sClientProviderWithLogger(k8sLogger), tokenUpdater: newTokenUpdaterController(), builder: buildv2.NewBuilderFromScratch(ioCtrl, onBuildFinish), - autoDown: newAutoDown(ioCtrl, k8sLogger, at), + autoDown: newAutoDown(ioCtrl, k8sLogger, at, upMeta), } up.inFd, up.isTerm = term.GetFdInfo(os.Stdin) if up.isTerm { diff --git a/pkg/analytics/up.go b/pkg/analytics/up.go index 2a3a9a547ab0..dbffb2166f67 100644 --- a/pkg/analytics/up.go +++ b/pkg/analytics/up.go @@ -59,6 +59,7 @@ type UpMetricsMetadata struct { errSyncLostSyncthing bool success bool hasRunDeploy bool + isAutoDownEnabled bool } // NewUpMetricsMetadata returns an empty instance of UpMetricsMetadata @@ -92,6 +93,7 @@ func (u *UpMetricsMetadata) toProps() map[string]interface{} { "contextSyncDurationSeconds": u.contextSyncDuration.Seconds(), "localFoldersScanDurationSeconds": u.localFoldersScanDuration.Seconds(), "execDurationSeconds": u.execDuration.Seconds(), + "isAutoDownEnabled": u.isAutoDownEnabled, } } @@ -191,6 +193,10 @@ func (u *UpMetricsMetadata) ExecDuration(duration time.Duration) { u.execDuration = duration } +func (u *UpMetricsMetadata) IsAutoDownEnabled(enabled bool) { + u.isAutoDownEnabled = enabled +} + // TrackUp sends a tracking event to mixpanel when the user activates a development container func (a *Tracker) TrackUp(m *UpMetricsMetadata) { a.trackFn(upEvent, m.success, m.toProps()) diff --git a/pkg/analytics/up_test.go b/pkg/analytics/up_test.go index 238d8dcc3cd8..1cf5a7e80240 100644 --- a/pkg/analytics/up_test.go +++ b/pkg/analytics/up_test.go @@ -264,6 +264,7 @@ func Test_UpTracker(t *testing.T) { "oktetoCtxConfigDurationSeconds": float64(0), "errSyncInsufficientSpace": false, "errSyncLostSyncthing": false, + "isAutoDownEnabled": false, }, }, }, @@ -299,6 +300,7 @@ func Test_UpTracker(t *testing.T) { "oktetoCtxConfigDurationSeconds": float64(0), "errSyncInsufficientSpace": false, "errSyncLostSyncthing": false, + "isAutoDownEnabled": false, }, }, }, @@ -351,6 +353,7 @@ func Test_UpTracker(t *testing.T) { "oktetoCtxConfigDurationSeconds": float64(60), "errSyncInsufficientSpace": false, "errSyncLostSyncthing": false, + "isAutoDownEnabled": false, }, }, }, @@ -400,6 +403,7 @@ func Test_UpTracker(t *testing.T) { "oktetoCtxConfigDurationSeconds": float64(0), "errSyncInsufficientSpace": false, "errSyncLostSyncthing": false, + "isAutoDownEnabled": false, }, }, }, @@ -448,6 +452,7 @@ func Test_UpTracker(t *testing.T) { "oktetoCtxConfigDurationSeconds": float64(0), "errSyncInsufficientSpace": false, "errSyncLostSyncthing": false, + "isAutoDownEnabled": false, }, }, }, From a085776bbe036b4964e3f1ad148befd49ac91f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Mon, 27 Oct 2025 16:50:26 +0100 Subject: [PATCH 17/21] improvement: Only add the args from dependencies in the build (#4805) - Introduced new tests for the AddArgs method in the Info struct to validate the handling of build arguments based on the depends_on configuration. - Added a normalizeServiceName function to convert service names from environment variable format to the depends_on format. - Implemented isServiceInDependencies method to check if a service is included in the depends_on list, enhancing the logic for managing build arguments. These changes improve the robustness of the build argument handling and ensure accurate service dependency management in the build process. Signed-off-by: Javier Lopez --- pkg/build/info.go | 32 +++++++ pkg/build/info_test.go | 205 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) diff --git a/pkg/build/info.go b/pkg/build/info.go index 8750417af520..cc048cae9fbc 100644 --- a/pkg/build/info.go +++ b/pkg/build/info.go @@ -17,6 +17,7 @@ import ( "net/url" "os" "path/filepath" + "regexp" "strings" "github.com/okteto/okteto/pkg/cache" @@ -24,6 +25,9 @@ import ( oktetoLog "github.com/okteto/okteto/pkg/log" ) +// Regex to match OKTETO_BUILD__ pattern +var buildEnvVarRegex = regexp.MustCompile(`^OKTETO_BUILD_(.+)_(REGISTRY|REPOSITORY|IMAGE|TAG|SHA)$`) + // Info represents the build info to generate an image type Info struct { Secrets Secrets `yaml:"secrets,omitempty"` @@ -55,15 +59,43 @@ type infoRaw struct { DependsOn DependsOn `yaml:"depends_on,omitempty"` } +// normalizeServiceName converts a service name from env var format back to depends_on format +// e.g., "SVC_1" -> "svc-1", "API" -> "api" +func normalizeServiceName(serviceName string) string { + return strings.ToLower(strings.ReplaceAll(serviceName, "_", "-")) +} + +// isServiceInDependencies checks if a service is in the depends_on list +func (i *Info) isServiceInDependencies(serviceName string) bool { + normalizedService := normalizeServiceName(serviceName) + for _, dep := range i.DependsOn { + if dep == normalizedService { + return true + } + } + return false +} + func (i *Info) addExpandedPreviousImageArgs(previousImageArgs map[string]string) error { alreadyAddedArg := map[string]bool{} for _, arg := range i.Args { alreadyAddedArg[arg.Name] = true } + for k, v := range previousImageArgs { if _, ok := alreadyAddedArg[k]; ok { continue } + + // Check if this is a build environment variable and if the service is in dependencies + if matches := buildEnvVarRegex.FindStringSubmatch(k); matches != nil && len(matches) > 1 { + serviceName := matches[1] + if !i.isServiceInDependencies(serviceName) { + oktetoLog.Debugf("Skipping '%s' - service '%s' not in depends_on list", k, normalizeServiceName(serviceName)) + continue + } + } + expandedValue, err := env.ExpandEnv(v) if err != nil { return err diff --git a/pkg/build/info_test.go b/pkg/build/info_test.go index e1ad21ea7133..b49093533e9a 100644 --- a/pkg/build/info_test.go +++ b/pkg/build/info_test.go @@ -481,3 +481,208 @@ func Test_expandSecrets(t *testing.T) { }) } } + +func TestAddArgsWithDependsOnFiltering(t *testing.T) { + tests := []struct { + name string + buildInfo *Info + previousImageArgs map[string]string + expectedArgsCount int + expectedArgs []string + shouldNotContain []string + }{ + { + name: "adds build args for services in depends_on", + buildInfo: &Info{ + DependsOn: DependsOn{"api", "frontend"}, + }, + previousImageArgs: map[string]string{ + "OKTETO_BUILD_API_IMAGE": "registry.com/api:latest", + "OKTETO_BUILD_API_REGISTRY": "registry.com", + "OKTETO_BUILD_FRONTEND_IMAGE": "registry.com/frontend:latest", + }, + expectedArgsCount: 3, + expectedArgs: []string{ + "OKTETO_BUILD_API_IMAGE", + "OKTETO_BUILD_API_REGISTRY", + "OKTETO_BUILD_FRONTEND_IMAGE", + }, + }, + { + name: "skips build args for services not in depends_on", + buildInfo: &Info{ + DependsOn: DependsOn{"api"}, + }, + previousImageArgs: map[string]string{ + "OKTETO_BUILD_API_IMAGE": "registry.com/api:latest", + "OKTETO_BUILD_FRONTEND_IMAGE": "registry.com/frontend:latest", + "OKTETO_BUILD_DATABASE_IMAGE": "registry.com/database:latest", + }, + expectedArgsCount: 1, + expectedArgs: []string{ + "OKTETO_BUILD_API_IMAGE", + }, + shouldNotContain: []string{ + "OKTETO_BUILD_FRONTEND_IMAGE", + "OKTETO_BUILD_DATABASE_IMAGE", + }, + }, + { + name: "handles services with hyphens in depends_on", + buildInfo: &Info{ + DependsOn: DependsOn{"svc-1", "my-service"}, + }, + previousImageArgs: map[string]string{ + "OKTETO_BUILD_SVC_1_IMAGE": "registry.com/svc-1:latest", + "OKTETO_BUILD_SVC_1_REGISTRY": "registry.com", + "OKTETO_BUILD_MY_SERVICE_IMAGE": "registry.com/my-service:latest", + "OKTETO_BUILD_OTHER_IMAGE": "registry.com/other:latest", + }, + expectedArgsCount: 3, + expectedArgs: []string{ + "OKTETO_BUILD_SVC_1_IMAGE", + "OKTETO_BUILD_SVC_1_REGISTRY", + "OKTETO_BUILD_MY_SERVICE_IMAGE", + }, + shouldNotContain: []string{ + "OKTETO_BUILD_OTHER_IMAGE", + }, + }, + { + name: "adds non-build environment variables regardless of depends_on", + buildInfo: &Info{ + DependsOn: DependsOn{"api"}, + }, + previousImageArgs: map[string]string{ + "OKTETO_BUILD_API_IMAGE": "registry.com/api:latest", + "OKTETO_BUILD_OTHER_IMAGE": "registry.com/other:latest", + "REGULAR_ENV_VAR": "some-value", + "ANOTHER_ENV_VAR": "another-value", + }, + expectedArgsCount: 3, + expectedArgs: []string{ + "OKTETO_BUILD_API_IMAGE", + "REGULAR_ENV_VAR", + "ANOTHER_ENV_VAR", + }, + shouldNotContain: []string{ + "OKTETO_BUILD_OTHER_IMAGE", + }, + }, + { + name: "empty depends_on skips all build args", + buildInfo: &Info{ + DependsOn: DependsOn{}, + }, + previousImageArgs: map[string]string{ + "OKTETO_BUILD_API_IMAGE": "registry.com/api:latest", + "OKTETO_BUILD_FRONTEND_IMAGE": "registry.com/frontend:latest", + "REGULAR_ENV_VAR": "some-value", + }, + expectedArgsCount: 1, + expectedArgs: []string{ + "REGULAR_ENV_VAR", + }, + shouldNotContain: []string{ + "OKTETO_BUILD_API_IMAGE", + "OKTETO_BUILD_FRONTEND_IMAGE", + }, + }, + { + name: "handles all build env var types", + buildInfo: &Info{ + DependsOn: DependsOn{"api"}, + }, + previousImageArgs: map[string]string{ + "OKTETO_BUILD_API_REGISTRY": "registry.com", + "OKTETO_BUILD_API_REPOSITORY": "myorg/api", + "OKTETO_BUILD_API_IMAGE": "registry.com/myorg/api:latest", + "OKTETO_BUILD_API_TAG": "latest", + "OKTETO_BUILD_API_SHA": "abc123", + }, + expectedArgsCount: 5, + expectedArgs: []string{ + "OKTETO_BUILD_API_REGISTRY", + "OKTETO_BUILD_API_REPOSITORY", + "OKTETO_BUILD_API_IMAGE", + "OKTETO_BUILD_API_TAG", + "OKTETO_BUILD_API_SHA", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.buildInfo.AddArgs(tt.previousImageArgs) + require.NoError(t, err) + + // Check expected args count + assert.Equal(t, tt.expectedArgsCount, len(tt.buildInfo.Args)) + + // Check that expected args are present + argNames := make([]string, len(tt.buildInfo.Args)) + for i, arg := range tt.buildInfo.Args { + argNames[i] = arg.Name + } + + for _, expectedArg := range tt.expectedArgs { + assert.Contains(t, argNames, expectedArg, "Expected arg %s not found", expectedArg) + } + + // Check that unwanted args are not present + for _, unwantedArg := range tt.shouldNotContain { + assert.NotContains(t, argNames, unwantedArg, "Unwanted arg %s found", unwantedArg) + } + }) + } +} + +func TestNormalizeServiceName(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"API", "api"}, + {"SVC_1", "svc-1"}, + {"MY_SERVICE", "my-service"}, + {"FRONTEND", "frontend"}, + {"BACK_END_SERVICE", "back-end-service"}, + {"SERVICE_WITH_MULTIPLE_UNDERSCORES", "service-with-multiple-underscores"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := normalizeServiceName(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestIsServiceInDependencies(t *testing.T) { + info := &Info{ + DependsOn: DependsOn{"api", "svc-1", "my-service"}, + } + + tests := []struct { + serviceName string + expected bool + }{ + {"API", true}, + {"api", true}, + {"SVC_1", true}, + {"svc-1", true}, + {"MY_SERVICE", true}, + {"my-service", true}, + {"FRONTEND", false}, + {"frontend", false}, + {"DATABASE", false}, + {"database", false}, + } + + for _, tt := range tests { + t.Run(tt.serviceName, func(t *testing.T) { + result := info.isServiceInDependencies(tt.serviceName) + assert.Equal(t, tt.expected, result) + }) + } +} From 127a22387a98a9c84266327d5afb4f9e4dd26dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Mon, 27 Oct 2025 17:02:17 +0100 Subject: [PATCH 18/21] refactor: remove the GetServiceHash function (#4806) * refactor: remove the GetServiceHash function Signed-off-by: Javier Lopez * refactor: rename GetServiceHash to GetBuildHash Updated the method name from GetServiceHash to GetBuildHash in both the build and test files to reflect the new functionality. This change improves clarity and consistency in the codebase. Signed-off-by: Javier Lopez --------- Signed-off-by: Javier Lopez --- cmd/build/v2/build.go | 2 +- cmd/build/v2/smartbuild/smartbuild.go | 6 ------ cmd/build/v2/smartbuild/smartbuild_test.go | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/cmd/build/v2/build.go b/cmd/build/v2/build.go index 66af8d851dd8..3b5b09b3bd46 100644 --- a/cmd/build/v2/build.go +++ b/cmd/build/v2/build.go @@ -261,7 +261,7 @@ func (ob *OktetoBuilder) Build(ctx context.Context, options *types.BuildOptions) buildContextHashDurationStart := time.Now() - serviceHash := ob.smartBuildCtrl.GetServiceHash(buildSvcInfo, svcToBuild) + serviceHash := ob.smartBuildCtrl.GetBuildHash(buildSvcInfo, svcToBuild) meta.BuildContextHash = serviceHash meta.BuildContextHashDuration = time.Since(buildContextHashDurationStart) diff --git a/cmd/build/v2/smartbuild/smartbuild.go b/cmd/build/v2/smartbuild/smartbuild.go index 0a50b8992e79..4b0da688eecb 100644 --- a/cmd/build/v2/smartbuild/smartbuild.go +++ b/cmd/build/v2/smartbuild/smartbuild.go @@ -82,12 +82,6 @@ func (s *Ctrl) GetProjectHash(buildInfo *build.Info) (string, error) { return s.hasher.hashProjectCommit(buildInfo) } -// GetServiceHash returns the hash of the service -func (s *Ctrl) GetServiceHash(buildInfo *build.Info, service string) string { - s.ioCtrl.Logger().Debugf("getting service hash") - return s.hasher.hashWithBuildContext(buildInfo, service) -} - // GetBuildHash returns the hash of the build based on the env vars func (s *Ctrl) GetBuildHash(buildInfo *build.Info, service string) string { s.ioCtrl.Logger().Debugf("getting hash based on the buildContext env var") diff --git a/cmd/build/v2/smartbuild/smartbuild_test.go b/cmd/build/v2/smartbuild/smartbuild_test.go index c1d2b32cc977..625210ce3857 100644 --- a/cmd/build/v2/smartbuild/smartbuild_test.go +++ b/cmd/build/v2/smartbuild/smartbuild_test.go @@ -173,7 +173,7 @@ func TestGetServiceHash(t *testing.T) { hash: "hash", }, } - out := sbc.GetServiceHash(&build.Info{}, service) + out := sbc.GetBuildHash(&build.Info{}, service) assert.Equal(t, "hash", out) } From 72ab21d5470bc4bcf0466b4e6d6236510034c045 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:46:03 +0100 Subject: [PATCH 19/21] Updated the checkout method for circle jobs (#4807) (#4808) * Updated the checkout method for circle jobs * Changed mode for run-unit-test for codecov tool --------- (cherry picked from commit fc13f28ead326156cc439ccbc376572089e03d67) Signed-off-by: Nacho Fuertes Co-authored-by: Ignacio Fuertes --- .circleci/config.yml | 72 +++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0dacb4f18a40..5c3685ad8dda 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,7 +47,8 @@ jobs: executor: golang-ci resource_class: large steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - golangci-lint-cache-v2-{{ checksum ".golangci.yml" }}-{{ checksum "go.sum" }} @@ -62,7 +63,8 @@ jobs: executor: golang-ci resource_class: large steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v1-bin-pkg-cache-{{ checksum "go.sum" }} @@ -88,7 +90,8 @@ jobs: check-schema: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - attach_workspace: at: ./artifacts - run: @@ -106,7 +109,8 @@ jobs: run-unit-test: executor: golang-ci steps: - - checkout + - checkout: + method: full - restore_cache: keys: - v4-pkg-cache-{{ checksum "go.sum" }} @@ -133,7 +137,8 @@ jobs: name: win/server-2022 version: 2024.04.1 steps: - - checkout + - checkout: + method: blobless - run: name: Check Golang version command: go version @@ -150,7 +155,8 @@ jobs: e2e-actions: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v4-pkg-cache-{{ checksum "go.sum" }} @@ -169,7 +175,8 @@ jobs: e2e-build: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v4-pkg-cache-{{ checksum "go.sum" }} @@ -186,7 +193,8 @@ jobs: e2e-deploy: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v4-pkg-cache-{{ checksum "go.sum" }} @@ -208,7 +216,8 @@ jobs: e2e-up: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v4-pkg-cache-{{ checksum "go.sum" }} @@ -231,7 +240,8 @@ jobs: e2e-okteto: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v4-pkg-cache-{{ checksum "go.sum" }} @@ -252,7 +262,8 @@ jobs: e2e-okteto-test: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v4-pkg-cache-{{ checksum "go.sum" }} @@ -273,7 +284,8 @@ jobs: test-e2e-setup: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v4-pkg-cache-{{ checksum "go.sum" }} @@ -297,7 +309,8 @@ jobs: test-release: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - attach_workspace: at: ./artifacts - add_ssh_keys: @@ -325,7 +338,8 @@ jobs: OKTETO_NAMESPACE_PREFIX: integration OKTETO_SKIP_CLUSTER_CLI_VERSION: true steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v5-pkg-cache-windows-1-15-{{ checksum "go.sum" }} @@ -352,7 +366,8 @@ jobs: OKTETO_NAMESPACE_PREFIX: integration OKTETO_SKIP_CLUSTER_CLI_VERSION: true steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v5-pkg-cache-windows-1-15-{{ checksum "go.sum" }} @@ -389,7 +404,8 @@ jobs: OKTETO_NAMESPACE_PREFIX: integration OKTETO_SKIP_CLUSTER_CLI_VERSION: true steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v5-pkg-cache-windows-1-15-{{ checksum "go.sum" }} @@ -426,7 +442,8 @@ jobs: OKTETO_NAMESPACE_PREFIX: integration OKTETO_SKIP_CLUSTER_CLI_VERSION: true steps: - - checkout + - checkout: + method: blobless - restore_cache: keys: - v5-pkg-cache-windows-1-15-{{ checksum "go.sum" }} @@ -456,7 +473,8 @@ jobs: push-image-tag: executor: golang-ci steps: - - checkout + - checkout: + method: full - run: *docker-login - run: ./scripts/ci/push-image.sh "$CIRCLE_TAG" "linux/amd64,linux/arm64" - run: trivy image --db-repository public.ecr.aws/aquasecurity/trivy-db:2 --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db:1 --no-progress okteto/okteto:$CIRCLE_TAG @@ -464,7 +482,8 @@ jobs: push-image-dev: executor: golang-ci steps: - - checkout + - checkout: + method: full - run: *docker-login - run: ./scripts/ci/push-image.sh "$CIRCLE_TAG",dev "linux/amd64,linux/arm64" - run: trivy image --db-repository public.ecr.aws/aquasecurity/trivy-db:2 --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db:1 --no-progress okteto/okteto:dev @@ -472,7 +491,8 @@ jobs: push-image-master: executor: golang-ci steps: - - checkout + - checkout: + method: full - run: *docker-login - run: ./scripts/ci/push-image.sh "master" "linux/amd64,linux/arm64" - run: trivy image --db-repository public.ecr.aws/aquasecurity/trivy-db:2 --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db:1 --no-progress okteto/okteto:master @@ -480,14 +500,16 @@ jobs: upload-static-job: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - run: *init-gcloud - run: 'gsutil -m -h "Cache-Control: no-store" -h "Content-Type: text/x-sh" cp ./scripts/get-okteto.sh gs://get.okteto.com/get-okteto.sh' release-brew-formula: executor: golang-ci steps: - - checkout + - checkout: + method: blobless - attach_workspace: at: ./artifacts - run: *init-gcloud @@ -507,7 +529,8 @@ jobs: release-gh-actions: executor: golang-ci steps: - - checkout + - checkout: + method: full - run: *init-gcloud - add_ssh_keys: fingerprints: @@ -529,7 +552,8 @@ jobs: release-branch-job: executor: golang-ci steps: - - checkout + - checkout: + method: full - add_ssh_keys: fingerprints: # This key belongs to oktetobot user in GitHub From d5a461d95b15a0b618d9b1425cfa3a01efd20a14 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:04:22 +0100 Subject: [PATCH 20/21] add debug logs to troubleshoot context creation timeouts (#4810) (#4811) (cherry picked from commit e310c467ba82e0f4bdbc8e3eacfdcacbc59af1da) Co-authored-by: Ramiro Berrelleza --- cmd/context/create.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/context/create.go b/cmd/context/create.go index 16c708179fa3..19f014e9233a 100644 --- a/cmd/context/create.go +++ b/cmd/context/create.go @@ -141,6 +141,7 @@ func (c *Command) UseContext(ctx context.Context, ctxOptions *Options) error { } if ctxOptions.Save { + oktetoLog.Debug("check if user can access namespace") hasAccess, err := hasAccessToNamespace(ctx, c, ctxOptions) if err != nil { return err @@ -227,6 +228,7 @@ func hasAccessToNamespace(ctx context.Context, c *Command, ctxOptions *Options) } func (c *Command) initOktetoContext(ctx context.Context, ctxOptions *Options) error { + oktetoLog.Debug("initializing okteto context") var userContext *types.UserContext userContext, err := getLoggedUserContext(ctx, c, ctxOptions) if err != nil { @@ -254,6 +256,7 @@ func (c *Command) initOktetoContext(ctx context.Context, ctxOptions *Options) er ctxOptions.Namespace = userContext.User.Namespace } + oktetoLog.Debug("downloading okteto cluster metadata") clusterMetadata, err := getClusterMetadata(ctx, ctxOptions.Namespace, c.OktetoClientProvider) if err != nil { oktetoLog.Infof("error getting cluster metadata: %v", err) @@ -261,12 +264,14 @@ func (c *Command) initOktetoContext(ctx context.Context, ctxOptions *Options) er } // once we have namespace and user identify we are able to retrieve the dynamic token for the namespace + oktetoLog.Debug("updating okteto context token") err = c.kubetokenController.updateOktetoContextToken(userContext) if err != nil { // TODO: when the static token feature gets removed, we must return an error to the user here oktetoLog.Infof("error updating okteto context token: %v", err) } + oktetoLog.Debug("adding okteto context to %s", config.GetKubeconfigPath()) okteto.AddOktetoContext(ctxOptions.Context, &userContext.User, ctxOptions.Namespace, userContext.User.Namespace) cfg := kubeconfig.Get(config.GetKubeconfigPath()) if cfg == nil { From 06c8ea7cbe8a2f99f23b49925c3ee5cdf7c39f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=B3pez=20Barba?= Date: Wed, 5 Nov 2025 18:02:29 +0100 Subject: [PATCH 21/21] build: update Okteto components and Go version in Dockerfile and go.mod (#4814) (#4815) * build: update Okteto components and Go version in Dockerfile and go.mod - Bumped OKTETO_REMOTE_VERSION from 0.6.5 to 0.6.6 - Updated OKTETO_SUPERVISOR_VERSION from 1.0.0 to 1.0.1 - Increased OKTETO_CLEAN_VERSION from 0.2.4 to 0.2.5 - Upgraded GOLANG_VERSION from 1.24.7 to 1.24.9 in both Dockerfile and go.mod (cherry picked from commit 6a20f058c1f6f08e26e07adcdd07b12151d946d7) * update: golang ci to use the latest go version --------- (cherry picked from commit 1fb3905979e7ae3fec80d3e2a015415692b7be3f) Signed-off-by: Javier Lopez --- .circleci/config.yml | 2 +- Dockerfile | 9 ++++----- go.mod | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5c3685ad8dda..03dc5cdb2c2b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ orbs: executors: golang-ci: docker: - - image: okteto/golang-ci:2.8.2 + - image: okteto/golang-ci:2.8.3 environment: OKTETO_CONTEXT: https://okteto.integration.dev.okteto.net OKTETO_APPS_SUBDOMAIN: integration.dev.okteto.net diff --git a/Dockerfile b/Dockerfile index bb95d98ce36a..ac7c19a2d16c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,13 +5,12 @@ ARG HELM_VERSION=3.18.6 ARG KUSTOMIZE_VERSION=5.7.1 # Okteto components ARG SYNCTHING_VERSION=2.0.10 -ARG OKTETO_REMOTE_VERSION=0.6.5 +ARG OKTETO_REMOTE_VERSION=0.6.6 - -ARG OKTETO_SUPERVISOR_VERSION=1.0.0 -ARG OKTETO_CLEAN_VERSION=0.2.4 +ARG OKTETO_SUPERVISOR_VERSION=1.0.1 +ARG OKTETO_CLEAN_VERSION=0.2.5 # Base images -ARG GOLANG_VERSION=1.24.7 +ARG GOLANG_VERSION=1.24.9 ARG ALPINE_VERSION=3.20 ARG BUSYBOX_VERSION=1.36.1 ARG GIT_VERSION=2.42.0 diff --git a/go.mod b/go.mod index 9679760cd354..1ed14726f804 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/okteto/okteto -go 1.24.7 +go 1.24.9 require ( github.com/Masterminds/semver/v3 v3.2.0