Skip to content

Commit 447bb76

Browse files
docs(run): run workspace roots honour the environment's exclude-services; regression orders every root
The standing rule: a behaviour change updates docs/ in the same PR. `docs/commands.md` now says, under `codefly run workspace`, that the selected environment's `exclude-services` removes services from the composition roots — a module withdrawn whole contributes none, its service-entry included — and that CLI and run-profile exclusions still apply on top. The regression builds an isolated two-module workspace, selects `local` (which withdraws worker/witness) and `staging` (which does not), applies CLI and profile exclusions beside them, calls the real CompositionRoots, builds the real Core dependency graph with the same selected-environment filter the flow uses, and orders to every selected root — the exact shape that failed on main: `cannot topologically sort to <worker/witness>: service worker/witness is not in the graph`. Red on the pre-fix CompositionRoots, green on this branch (independent review, codex-cli1204-exclusion-review). Patch authored by the Codex independent reviewer; adopted unchanged after reading it, with `go test ./cmd/run/` and golangci-lint clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 92d2d42 commit 447bb76

2 files changed

Lines changed: 102 additions & 1 deletion

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package run
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/codefly-dev/cli/pkg/environments"
10+
"github.com/codefly-dev/cli/pkg/orchestration"
11+
"github.com/codefly-dev/core/architecture"
12+
"github.com/codefly-dev/core/resources"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
// A composition root must survive the same selected-environment exclusions
17+
// that orchestration applies when it builds the dependency graph.
18+
func TestCompositionRootsRespectSelectedEnvironmentExclusions(t *testing.T) {
19+
oldEnv, oldProfile, oldExcluded := environmentName, profile, excludeDependencies
20+
t.Cleanup(func() { environmentName, profile, excludeDependencies = oldEnv, oldProfile, oldExcluded })
21+
profile = "small"
22+
excludeDependencies = []string{"host/admin", "worker/cli-only"}
23+
root := t.TempDir()
24+
workspaceYAML := `name: roots
25+
layout: modules
26+
modules:
27+
- name: host
28+
- name: worker
29+
run-profiles:
30+
small:
31+
exclude-dependencies: [worker/profile-only]
32+
environments:
33+
- name: local
34+
host:
35+
coordinate: example/local/cell
36+
component: host
37+
domain: example
38+
audience: host
39+
trust_domain: cluster.example
40+
delivery: host/web/rest
41+
ingress:
42+
- name: web
43+
service: web
44+
endpoint: rest
45+
hosts: [web.example.test]
46+
- name: admin
47+
service: admin
48+
endpoint: rest
49+
hosts: [admin.example.test]
50+
exclude-services:
51+
- service: worker/witness
52+
- name: staging
53+
`
54+
for module, names := range map[string][]string{
55+
"host": {"web", "admin"},
56+
"worker": {"api", "witness", "cli-only", "profile-only"},
57+
} {
58+
dir := filepath.Join(root, "modules", module)
59+
require.NoError(t, os.MkdirAll(dir, 0o755))
60+
doc := "kind: module\nname: " + module + "\nservices:\n"
61+
for _, name := range names {
62+
doc += " - name: " + name + "\n"
63+
serviceDir := filepath.Join(dir, "services", name)
64+
require.NoError(t, os.MkdirAll(serviceDir, 0o755))
65+
service := fmt.Sprintf("kind: service\nname: %s\nmodule: %s\nversion: 0.0.0\nagent:\n kind: runtime::service\n name: go\n version: 0.0.1\n publisher: codefly.ai\nendpoints:\n - name: rest\n api: rest\n visibility: public\n exposure: none\n", name, module)
66+
require.NoError(t, os.WriteFile(filepath.Join(serviceDir, "service.codefly.yaml"), []byte(service), 0o600))
67+
}
68+
require.NoError(t, os.WriteFile(filepath.Join(dir, "module.codefly.yaml"), []byte(doc), 0o600))
69+
}
70+
require.NoError(t, os.WriteFile(filepath.Join(root, "workspace.codefly.yaml"), []byte(workspaceYAML), 0o600))
71+
workspace, err := resources.LoadWorkspaceFromDir(t.Context(), root)
72+
require.NoError(t, err)
73+
for _, tc := range []struct {
74+
env string
75+
want []string
76+
}{
77+
{"local", []string{"host/web", "worker/api"}},
78+
{"staging", []string{"host/web", "worker/api", "worker/witness"}},
79+
} {
80+
t.Run(tc.env, func(t *testing.T) {
81+
environmentName = tc.env
82+
roots, err := CompositionRoots(t.Context(), workspace)
83+
require.NoError(t, err)
84+
env, err := orchestration.SelectEnvironment(workspace, tc.env)
85+
require.NoError(t, err)
86+
require.NoError(t, environments.ValidateEnvironmentExclusions(t.Context(), workspace, env))
87+
graph, err := architecture.NewServiceDependencies(t.Context(), workspace, architecture.ExcludeServices(env.ExcludedServiceUniques()...))
88+
require.NoError(t, err)
89+
for _, root := range roots {
90+
_, err = graph.OrderTo(t.Context(), root)
91+
if err != nil {
92+
t.Errorf("selected composition root %s cannot be ordered: %v", root, err)
93+
}
94+
}
95+
require.Equal(t, tc.want, roots)
96+
})
97+
}
98+
}

‎docs/commands.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ service` that do not name one root service — `--naming-scope`,
118118
`--temporary-ports`, `--output-env`, `--runtime-context`, `--set` among them,
119119
never `--stand-alone` or `--exclude-root`; `--exclude-dependency` and
120120
`--exclude-workspace-configuration` leave a service or a configuration group
121-
out. The deterministic port hash folds every endpoint into a few thousand
121+
out. The selected environment's `exclude-services` also removes services from
122+
the composition roots; a module withdrawn whole contributes no root, including
123+
its service-entry. CLI and run-profile exclusions still apply on top.
124+
The deterministic port hash folds every endpoint into a few thousand
122125
buckets, so a `--naming-scope` can hash two endpoints to one port and abort
123126
the run; `--temporary-ports` sidesteps the hash, under the ordering its
124127
allocation needs — a workspace configuration reference to a service that has

0 commit comments

Comments
 (0)