From 03eca48ed8a116fa9d52d7c17e2ab7cb37ed30c4 Mon Sep 17 00:00:00 2001 From: Jared Watts Date: Mon, 10 Aug 2026 17:47:24 -0700 Subject: [PATCH 1/2] ci: fix Renovate's Nix tasks and let it update flake.lock Renovate's post-upgrade tasks fail here, so nix run .#tidy and nix run .#generate never refresh gomod2nix.toml or our generated code. The docker/cli bump in #253 was opened with go.mod and go.sum alone for this reason. Nix builds without its sandbox in Renovate's container, where HOME is /homeless-shelter on the real filesystem. A build that writes there creates the directory, and Nix then refuses to start any later build. pkgs.gomod2nix is one of the builds that creates it, and it comes from a dependency rather than our own Nix code, so we have nowhere to set a writable HOME. The entrypoint now registers a post-build hook that removes the directory after each build, and sets max-jobs to 1 so no build starts before the cleanup runs. The nix manager is disabled by default, so flake.lock has never been updated here and everything it pins is frozen. This commit enables the manager along with lock file maintenance, on an empty schedule so a refresh can open during any run, and gives it a prPriority that sorts it behind security updates only. Release branches opt out of non-security updates, but they build with Nix too and pick up newer Go versions through the lock file, so a rule re-enables maintenance for them. Renovate installs its own Nix through containerbase whenever it updates flake.lock. That one goes earlier on PATH and ignores /etc/nix/nix.conf, so the entrypoint publishes a crossplane-nix launcher that pins both the binary and the config it reads, and the post-upgrade tasks call it. RENOVATE_ALLOWED_COMMANDS no longer permits a bare nix, so a command still spelled that way fails on the allowlist instead of running against the wrong Nix. RENOVATE_VERSION now pins the version the validator runs and the version the action runs, so the two cannot disagree, and a customManager keeps it current. prConcurrentLimit returns to Renovate's default of 10 and the hourly PR limit is disabled so a backlog drains faster, security fixes skip dependency dashboard approval, and the entrypoint no longer installs Earthly, which nothing in this repo builds with. Signed-off-by: Jared Watts (cherry picked from commit 98b9db9565978ff1e4de49272c6c5c3c291c2edb) --- .github/renovate-entrypoint.sh | 37 +++++++++++----- .github/renovate.json5 | 77 +++++++++++++++++++++++++++++++--- .github/workflows/renovate.yml | 9 +++- 3 files changed, 106 insertions(+), 17 deletions(-) diff --git a/.github/renovate-entrypoint.sh b/.github/renovate-entrypoint.sh index a9f48b78..d6587dcf 100755 --- a/.github/renovate-entrypoint.sh +++ b/.github/renovate-entrypoint.sh @@ -2,18 +2,22 @@ set -e -# Install Earthly (for release branches) -echo "Installing Earthly..." -curl -fsSLo /usr/local/bin/earthly https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -chmod +x /usr/local/bin/earthly -/usr/local/bin/earthly bootstrap - -# Install Nix (for main branch) +# Install Nix. echo "Installing Nix..." apt-get update && apt-get install -y nix-bin # Configure Nix mkdir -p /etc/nix + +# The Renovate container can't run Nix sandboxed, so HOME=/homeless-shelter on +# the real filesystem. Any build that writes to $HOME creates that directory, +# and Nix then refuses to start another build until it's gone. +cat >/usr/local/bin/nix-clean-homeless-shelter <<'EOF' +#!/bin/sh +rm -rf /homeless-shelter +EOF +chmod +x /usr/local/bin/nix-clean-homeless-shelter + cat >/etc/nix/nix.conf <<'EOF' # Enable flakes and the nix command (e.g. nix run, nix build). experimental-features = nix-command flakes @@ -22,14 +26,27 @@ experimental-features = nix-command flakes # needing to create the nixbld group and users in this ephemeral container. build-users-group = -# Build derivations in parallel, one per CPU core. -max-jobs = auto +# One build at a time, so no build starts before we can clean up /homeless-shelter. +max-jobs = 1 + +# Removes /homeless-shelter after each build (see the hook script above). +post-build-hook = /usr/local/bin/nix-clean-homeless-shelter # Use the Crossplane Cachix cache to download pre-built binaries from CI. extra-substituters = https://crossplane.cachix.org extra-trusted-public-keys = crossplane.cachix.org-1:NJluVUN9TX0rY/zAxHYaT19Y5ik4ELH4uFuxje+62d4= EOF -echo "Nix $(nix --version) installed successfully" +# Renovate installs its own Nix when it updates flake.lock. It goes earlier on +# PATH than ours and ignores the config above, so all nix commands we run (e.g. +# postUpgradeTasks) will go through this launcher, which pins both the binary +# and the config it reads. +cat >/usr/local/bin/crossplane-nix <<'EOF' +#!/bin/bash +exec env NIX_CONF_DIR=/etc/nix /usr/bin/nix "$@" +EOF +chmod +x /usr/local/bin/crossplane-nix + +echo "Nix $(crossplane-nix --version) installed successfully" renovate diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1311faca..03cca7d4 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -8,8 +8,13 @@ // We only want renovate to rebase PRs when they have conflicts, default // "auto" mode is not required. rebaseWhen: 'conflicted', - // The maximum number of PRs to be created in parallel - prConcurrentLimit: 5, + // The maximum number of PRs to be created in parallel - branchConcurrentLimit + // inherits this value as well + prConcurrentLimit: 10, + // Disable the default 2 per hour PR creation limit. prConcurrentLimit already + // caps how many PRs can be open at once and our renovate job runs on a daily + // schedule, so having any hourly throttling can actually slow us down. + prHourlyLimit: 0, // The branches renovate should target // PLEASE UPDATE THIS WHEN RELEASING. baseBranches: [ @@ -24,9 +29,46 @@ labels: [ 'automated', ], + // Enable the nix manager to update flake.lock when flake inputs change. + nix: { + enabled: true, + }, + // Allow Renovate to update lock files, so we get new updates in Nix inputs + // (e.g., nixpkgs, nixpkgs-unstable) regularly. + // + // An empty schedule ("[]") means "at any time", so we can get a lock file + // maintenance PR during any run of Renovate. They can be important, so let's + // not throttle them unnecessarily. + lockFileMaintenance: { + enabled: true, + schedule: [], + }, + customManagers: [ + { + customType: 'regex', + description: 'Bump the Renovate version used by the config validator and the bot', + managerFilePatterns: [ + '/^\\.github/workflows/renovate\\.ya?ml$/', + ], + matchStrings: [ + 'RENOVATE_VERSION: "(?.*?)"', + ], + datasourceTemplate: 'npm', + depNameTemplate: 'renovate', + }, + ], // PackageRules disabled below should be enabled in case of vulnerabilities vulnerabilityAlerts: { enabled: true, + automerge: false, + // Security fixes shouldn't have to wait on dependency dashboard approval. + dependencyDashboardApproval: false, + addLabels: [ + 'security', + ], + // Set a static groupName for security fixes so they all get batched into a + // single PR per base branch, instead of one per CVE. + groupName: 'vulnerable dependencies', }, osvVulnerabilityAlerts: true, // Renovate evaluates all packageRules in order, so low priority rules should @@ -112,6 +154,31 @@ ], groupName: 'golang version', }, + { + // Give lock file maintenance PRs higher priority than other non-security + // related updates. + description: 'Sort lock file maintenance ahead of other updates', + matchUpdateTypes: [ + 'lockFileMaintenance', + ], + prPriority: 10, + }, + { + // We disable all non-security updates on release branches, but we still + // want Nix flake.lock to get updated there, since that's how we get newer + // versions of Go, which could have security fixes. + description: 'Refresh flake.lock on release branches too', + matchManagers: [ + 'nix', + ], + matchUpdateTypes: [ + 'lockFileMaintenance', + ], + matchBaseBranches: [ + '/^release-.*/', + ], + enabled: true, + }, { description: 'Regenerate gomod2nix.toml and generated code after upgrading go dependencies', matchDatasources: [ @@ -119,8 +186,8 @@ ], postUpgradeTasks: { commands: [ - 'nix run .#tidy', - 'nix run .#generate', + 'crossplane-nix run .#tidy', + 'crossplane-nix run .#generate', ], fileFilters: [ '**/*', @@ -135,7 +202,7 @@ ], postUpgradeTasks: { commands: [ - 'nix run .#lint', + 'crossplane-nix run .#lint', ], fileFilters: [ '**/*', diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 275f4fa5..913cae25 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -16,6 +16,10 @@ permissions: env: LOG_LEVEL: "info" + # The Renovate version, used by both the config validator and the bot itself + # (see renovate-version below), so validation can't disagree with what runs. + # Renovate keeps this current - see the customManager in renovate.json5. + RENOVATE_VERSION: "44.23.0" jobs: renovate: @@ -29,7 +33,7 @@ jobs: # Don't waste time starting Renovate if JSON is invalid - name: Validate Renovate JSON - run: npx --yes --package renovate -- renovate-config-validator + run: npx --yes --package "renovate@${RENOVATE_VERSION}" -- renovate-config-validator - name: Get token id: get-github-app-token @@ -45,9 +49,10 @@ jobs: # Use GitHub API to create commits RENOVATE_PLATFORM_COMMIT: "true" LOG_LEVEL: ${{ github.event.inputs.logLevel || env.LOG_LEVEL }} - RENOVATE_ALLOWED_COMMANDS: '["^nix .+"]' + RENOVATE_ALLOWED_COMMANDS: '["^crossplane-nix .+"]' with: configurationFile: .github/renovate.json5 + renovate-version: ${{ env.RENOVATE_VERSION }} token: '${{ steps.get-github-app-token.outputs.token }}' mount-docker-socket: true docker-user: root From 2d1bc44b0eac01235af28de9ea0fab2170a28284 Mon Sep 17 00:00:00 2001 From: Jared Watts Date: Mon, 10 Aug 2026 17:48:06 -0700 Subject: [PATCH 2/2] build: bump nixpkgs from nixos-25.11 to nixos-26.05 The nixos-25.11 branch stopped moving at the end of June 2026 when the channel reached end of life, so every tool we pin through it is frozen. This commit points the nixpkgs input at nixos-26.05 and refreshes both inputs in flake.lock, neither of which had moved since April. Moving the channel moves the toolchain, and that accounts for the rest of the diff. protoc-gen-go 1.36.10 to 1.36.11 regenerates the render protobuf output. golangci-lint 2.11.4 to 2.12.2, which arrives with the nixpkgs-unstable refresh, reports new goconst and modernize findings, each addressed in place. nixpkgs 26.05 makes nixfmt-rfc-style an alias of nixfmt and warns when it is used, so we call nixfmt directly. Signed-off-by: Jared Watts (cherry picked from commit 79e0ba3b3f7970f40529b7df03fb040146b6d15c) --- .../trace/internal/printer/default.go | 5 +-- cmd/crossplane/xrd/generate.go | 31 ++++++++++++------- flake.lock | 14 ++++----- flake.nix | 4 +-- nix/apps.nix | 2 +- nix/checks.nix | 2 +- proto/render/v1alpha1/render.pb.go | 2 +- 7 files changed, 34 insertions(+), 26 deletions(-) diff --git a/cmd/crossplane/trace/internal/printer/default.go b/cmd/crossplane/trace/internal/printer/default.go index 427c07a2..b0412155 100644 --- a/cmd/crossplane/trace/internal/printer/default.go +++ b/cmd/crossplane/trace/internal/printer/default.go @@ -19,6 +19,7 @@ package printer import ( "fmt" "io" + "slices" "strings" "text/tabwriter" @@ -259,9 +260,9 @@ func (p *DefaultPrinter) printResourceTree(tw *tabwriter.Writer, root *resource. // Enqueue the children of the current node in reverse order to ensure // that they are dequeued from the LIFO queue in the same order w.r.t. // the way they are defined by the resources. - for idx := len(item.resource.Children) - 1; idx >= 0; idx-- { + for idx, child := range slices.Backward(item.resource.Children) { isLast := idx == len(item.resource.Children)-1 - queue = append(queue, &queueItem{resource: item.resource.Children[idx], depth: item.depth + 1, isLast: isLast, prefix: childPrefix}) + queue = append(queue, &queueItem{resource: child, depth: item.depth + 1, isLast: isLast, prefix: childPrefix}) } } return nil diff --git a/cmd/crossplane/xrd/generate.go b/cmd/crossplane/xrd/generate.go index 8bbca638..57b8acef 100644 --- a/cmd/crossplane/xrd/generate.go +++ b/cmd/crossplane/xrd/generate.go @@ -47,6 +47,13 @@ import ( //go:embed help/generate.md var generateHelp string +const ( + schemaTypeObject = "object" + + schemaFieldSpec = "spec" + schemaFieldStatus = "status" +) + type generateCmd struct { File string `arg:"" help:"Path to the XR or XRC YAML file."` From string `default:"xr" enum:"xr,simpleschema" help:"Input format: xr or simpleschema."` @@ -191,7 +198,7 @@ func replaceCELWithPlaceholder(data map[string]any) map[string]any { for key, value := range data { if isCELExpression(value) { - result[key] = "object" + result[key] = schemaTypeObject } else if nestedMap, ok := value.(map[string]any); ok { result[key] = replaceCELWithPlaceholder(nestedMap) } else { @@ -254,7 +261,7 @@ func newXRDFromSimpleSchema(yamlData []byte, customPlural string) (*v2.Composite return nil, errors.Wrap(err, "failed to convert spec to OpenAPI schema") } - statusSchema := &extv1.JSONSchemaProps{Type: "object", Properties: map[string]extv1.JSONSchemaProps{}} + statusSchema := &extv1.JSONSchemaProps{Type: schemaTypeObject, Properties: map[string]extv1.JSONSchemaProps{}} if len(simpleInput.Status) > 0 { celPaths := findCELFields(simpleInput.Status, nil) processedStatus := replaceCELWithPlaceholder(simpleInput.Status) @@ -269,12 +276,12 @@ func newXRDFromSimpleSchema(yamlData []byte, customPlural string) (*v2.Composite openAPIV3Schema := &extv1.JSONSchemaProps{ Description: fmt.Sprintf("%s is the Schema for the %s API.", kind, kind), - Type: "object", + Type: schemaTypeObject, Properties: map[string]extv1.JSONSchemaProps{ - "spec": *specSchema, - "status": *statusSchema, + schemaFieldSpec: *specSchema, + schemaFieldStatus: *statusSchema, }, - Required: []string{"spec"}, + Required: []string{schemaFieldSpec}, } schemaBytes, err := json.Marshal(openAPIV3Schema) @@ -399,20 +406,20 @@ func newXRDFromExample(yamlData []byte, customPlural string) (*v2.CompositeResou openAPIV3Schema := &extv1.JSONSchemaProps{ Description: description, - Type: "object", + Type: schemaTypeObject, Properties: map[string]extv1.JSONSchemaProps{ - "spec": { + schemaFieldSpec: { Description: fmt.Sprintf("%sSpec defines the desired state of %s.", kind, kind), - Type: "object", + Type: schemaTypeObject, Properties: specProps, }, - "status": { + schemaFieldStatus: { Description: fmt.Sprintf("%sStatus defines the observed state of %s.", kind, kind), - Type: "object", + Type: schemaTypeObject, Properties: statusProps, }, }, - Required: []string{"spec"}, + Required: []string{schemaFieldSpec}, } schemaBytes, err := json.Marshal(openAPIV3Schema) diff --git a/flake.lock b/flake.lock index 283dbd07..b7ccc11b 100644 --- a/flake.lock +++ b/flake.lock @@ -42,27 +42,27 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776221942, - "narHash": "sha256-FbQAeVNi7G4v3QCSThrSAAvzQTmrmyDLiHNPvTF2qFM=", + "lastModified": 1786313170, + "narHash": "sha256-9BG7OgUWdu0ONDO5X2q6+K4bsuBITkX/3W4nNJu1Ito=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1766437c5509f444c1b15331e82b8b6a9b967000", + "rev": "fcb8fcd6bf2d0adecae5bd491afaaaf8311b758d", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-25.11", + "ref": "nixos-26.05", "repo": "nixpkgs", "type": "github" } }, "nixpkgs-unstable": { "locked": { - "lastModified": 1776255774, - "narHash": "sha256-psVTpH6PK3q1htMJpmdz1hLF5pQgEshu7gQWgKO6t6Y=", + "lastModified": 1786098110, + "narHash": "sha256-shi1tjDhCGGd0kIgVPNY03V8NBWSTzhMSFDb7IqSoec=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "566acc07c54dc807f91625bb286cb9b321b5f42a", + "rev": "afb4584a80bbf779ce0f691509ff902d188c2b3d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index fa802c28..7cd4a05f 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ description = "Crossplane CLI"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; # TODO(negz): Unpin once https://github.com/nix-community/gomod2nix/pull/231 is released. @@ -181,7 +181,7 @@ pkgs.kubernetes-controller-tools # Nix - pkgs.nixfmt-rfc-style + pkgs.nixfmt ]; shellHook = '' diff --git a/nix/apps.nix b/nix/apps.nix index 1643e30c..4de643be 100644 --- a/nix/apps.nix +++ b/nix/apps.nix @@ -44,7 +44,7 @@ pkgs.unstable.golangci-lint pkgs.statix pkgs.deadnix - pkgs.nixfmt-rfc-style + pkgs.nixfmt pkgs.shellcheck pkgs.gnupatch pkgs.shfmt diff --git a/nix/checks.nix b/nix/checks.nix index b5cc5239..f2284dd6 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -144,7 +144,7 @@ nativeBuildInputs = [ pkgs.statix pkgs.deadnix - pkgs.nixfmt-rfc-style + pkgs.nixfmt ]; } '' diff --git a/proto/render/v1alpha1/render.pb.go b/proto/render/v1alpha1/render.pb.go index 58aab1f9..10ce2590 100644 --- a/proto/render/v1alpha1/render.pb.go +++ b/proto/render/v1alpha1/render.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 +// protoc-gen-go v1.36.11 // protoc (unknown) // source: proto/render/v1alpha1/render.proto