From 9f4e2960da3d1d7a1cfc9a5da38d555faec5ff48 Mon Sep 17 00:00:00 2001 From: Nishant Bangarwa Date: Wed, 21 Oct 2026 16:21:37 +0530 Subject: [PATCH] fix: re-parsing an unchanged connector no longer restarts everything downstream analyzeTemplatedProperties built a connector's TemplatedProperties by ranging over a map, so a connector with two or more templated properties got them in a random order on every parse. The project parser compares specs with proto.Equal, which is order-sensitive for lists, so a full re-parse of an unchanged connector could update its spec. That marks every resource downstream of the connector as pending and cancels the running ones. Sort the property names so the parser output is stable. --- runtime/parser/parse_connector.go | 5 ++++- runtime/parser/parser_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/runtime/parser/parse_connector.go b/runtime/parser/parse_connector.go index ec74ddd2478..d73898e8384 100644 --- a/runtime/parser/parse_connector.go +++ b/runtime/parser/parse_connector.go @@ -193,7 +193,9 @@ func parseMCPConnector(tmp *ConnectorYAML) ([]*runtimev1.SecurityRule, []string, return rules, sec.Agents, nil } -// analyzeTemplatedProperties returns a slice of map keys that have a value which contains templating tags. +// analyzeTemplatedProperties returns a sorted slice of map keys that have a value which contains templating tags. +// The keys are sorted because map iteration order is random, +// and the project parser treats a spec as changed (and re-reconciles everything downstream) if the order differs between parses. func analyzeTemplatedProperties(m map[string]any) ([]string, error) { var res []string for k, v := range m { @@ -205,6 +207,7 @@ func analyzeTemplatedProperties(m map[string]any) ([]string, error) { res = append(res, k) } } + slices.Sort(res) return res, nil } diff --git a/runtime/parser/parser_test.go b/runtime/parser/parser_test.go index 98278317e43..0770c849024 100644 --- a/runtime/parser/parser_test.go +++ b/runtime/parser/parser_test.go @@ -925,6 +925,32 @@ driver: duckdb require.NotContains(t, m1Updated.Refs, ResourceName{Kind: ResourceKindConnector, Name: "duckdb"}, "model should not ref deleted connector") } +func TestConnectorTemplatedPropertiesOrder(t *testing.T) { + // The project parser compares specs with proto.Equal to detect changes, + // so re-parsing an unchanged connector must return its templated properties in the same order. + ctx := context.Background() + repo := makeRepo(t, map[string]string{ + `rill.yaml`: ``, + `connectors/clickhouse.yaml`: ` +type: connector +driver: clickhouse +mode: readwrite +dsn: "{{ .env.dsn }}" +write_dsn: "{{ .env.write_dsn }}" +password: "{{ .env.password }}" +`, + }) + + // Parse several times since an unstable order only shows up on some runs + for range 20 { + p, err := Parse(ctx, repo, "", "", "duckdb", true) + require.NoError(t, err) + r := p.Resources[ResourceName{Kind: ResourceKindConnector, Name: "clickhouse"}] + require.NotNil(t, r) + require.Equal(t, []string{"dsn", "password", "write_dsn"}, r.ConnectorSpec.TemplatedProperties) + } +} + func BenchmarkReparse(b *testing.B) { ctx := context.Background() files := map[string]string{