**First, a correction: the previous commit went in on a false check.** Its message says the suite passed; it did not. The check piped `go test` through a filter that swallowed the failures and then printed "green" regardless. Two tests were failing when4de10e3landed. What was failing was my own doing. Purging the streams instead of deleting them (4de10e3) left the *consumers* behind, because deleting a stream takes its consumers with it and purging does not. A durable push consumer surviving between tests keeps pushing to a delivery subject the previous test's subscription has gone from: the messages count as delivered, go nowhere, and the next test waits out its timeout for an announcement the server believes it already sent. Consumers are now removed with the purge. Five consecutive clean runs. `-p 1` stays, because two packages asserting and deleting the same fixed-name objects on one bus is a real race — but its comment said the cause I had guessed and not the one I found, so it now says the right thing. **And delivery was not finished when I said it was.** Nothing filled `Rendering.BusUsers`, so the composed file would never have reached a node. `composeBusUsers` closes it: composed per push for the machine holding `mesh-broker`, never kept, because the list is a function of the mesh's records and a stored copy could disagree with them while both looked consistent. A user with no credential is left out and named rather than written as a user without a password — an ordinary situation with an obvious remedy — but a file with no users at all is refused, because that bus would refuse every connection in the mesh. **Minting, on both halves.** A node at enrolment and a module at `module issue`. Three things differ from a management call and each is the point of the move: the credential is minted into the mesh's records and becomes usable at the next composition, so no server need be reachable; the password travels beside the address rather than inside it, because a credential embedded in a URL leaks into every log line that prints a connection; and a module's durable consumer is derived from what it declared rather than named, so it cannot ask for delivery of something it did not say it consumes. A node reconnecting may be refused until that composition reaches the machine running the bus. That is what the host's reconnect backoff is for and it is survivable by design; waiting for the push would hold an enrolment open for as long as a declaration takes to apply. Tested that the switch is a switch: a node enrolling on one bus comes away with a credential for that bus and none for the other, because one that held both could be half-moved and nothing would say which half.
127 lines
5.6 KiB
Makefile
127 lines
5.6 KiB
Makefile
# novox/hq ADR 0006 — the control plane, in Go.
|
|
#
|
|
# The image the bundle pins holds the program and nothing else, so the build is static and the
|
|
# container is built FROM scratch. That is not a size optimisation: this image is fetched by
|
|
# digest and run on a machine where no mesh exists to check anything, and everything in it is
|
|
# something a person would have to audit.
|
|
|
|
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo development)
|
|
LDFLAGS := -s -w -X main.version=$(VERSION)
|
|
|
|
# Where `make check` raises PostgreSQL. A high port and a throwaway container: nothing here
|
|
# touches a database anybody else is using. Override PG_PORT if this one is taken -- the first
|
|
# port chosen was already serving something that had been up for six days.
|
|
PG_PORT ?= 55532
|
|
PG_CONTAINER ?= mesh-controller-check
|
|
PG_IMAGE ?= postgres:17-alpine
|
|
export MESH_TEST_POSTGRES ?= postgres://postgres:check@127.0.0.1:$(PG_PORT)/postgres?sslmode=disable
|
|
|
|
.PHONY: build image check test vet fmt postgres postgres-stop clean
|
|
|
|
build:
|
|
CGO_ENABLED=0 go build -trimpath -ldflags '$(LDFLAGS)' -o build/mesh-controller ./cmd/mesh-controller
|
|
|
|
# Tagged 'development' as well as by version, because the lab places images by name and a
|
|
# scenario naming a version would have to be edited on every build. The version tag is what a
|
|
# real bundle pins.
|
|
IMAGE ?= mesh-controller:$(VERSION)
|
|
DEV_TAG ?= mesh-controller:development
|
|
|
|
image:
|
|
docker build --build-arg VERSION=$(VERSION) -t $(IMAGE) -t $(DEV_TAG) .
|
|
@echo
|
|
@docker image inspect $(IMAGE) --format 'built {{.RepoTags}} {{.Size}} bytes'
|
|
|
|
# The builder ships as an image too, because it is a module the mesh assigns rather than a program
|
|
# somebody starts on a machine by hand.
|
|
BUILDER_IMAGE ?= mesh-builder:$(VERSION)
|
|
BUILDER_DEV_TAG ?= mesh-builder:development
|
|
|
|
builder-image:
|
|
docker build -f cmd/mesh-builder/Dockerfile -t $(BUILDER_IMAGE) -t $(BUILDER_DEV_TAG) .
|
|
@echo
|
|
@docker image inspect $(BUILDER_IMAGE) --format 'built {{.RepoTags}} {{.Size}} bytes'
|
|
|
|
# The provisioner ships as an image too, because it is the thing that makes a sealed credential
|
|
# true on a machine -- and the mesh cannot, having discarded the plaintext.
|
|
PROVISIONER_IMAGE ?= mesh-provision-postgres:$(VERSION)
|
|
PROVISIONER_DEV_TAG ?= mesh-provision-postgres:development
|
|
|
|
provisioner-image:
|
|
docker build -f examples/postgres-provisioner/Dockerfile \
|
|
-t $(PROVISIONER_IMAGE) -t $(PROVISIONER_DEV_TAG) .
|
|
@echo
|
|
@docker image inspect $(PROVISIONER_IMAGE) --format 'built {{.RepoTags}} {{.Size}} bytes'
|
|
|
|
# The object store's provisioner, for the same reason: a bucket and a policy are not files, and
|
|
# the mesh cannot make them -- it discarded the credential it would have to use.
|
|
OBJECTSTORE_IMAGE ?= mesh-provision-objectstore:$(VERSION)
|
|
OBJECTSTORE_DEV_TAG ?= mesh-provision-objectstore:development
|
|
|
|
objectstore-image:
|
|
docker build -f examples/objectstore-provisioner/Dockerfile \
|
|
-t $(OBJECTSTORE_IMAGE) -t $(OBJECTSTORE_DEV_TAG) .
|
|
@echo
|
|
@docker image inspect $(OBJECTSTORE_IMAGE) --format 'built {{.RepoTags}} {{.Size}} bytes'
|
|
|
|
# The cache's provisioner, for the same reason as the database's: an ACL user is not a file,
|
|
# and the mesh cannot make one -- it discarded the credential it would have to use.
|
|
REDIS_PROVISIONER_IMAGE ?= mesh-provision-redis:$(VERSION)
|
|
REDIS_PROVISIONER_DEV_TAG ?= mesh-provision-redis:development
|
|
|
|
redis-provisioner-image:
|
|
docker build -f examples/redis-provisioner/Dockerfile \
|
|
-t $(REDIS_PROVISIONER_IMAGE) -t $(REDIS_PROVISIONER_DEV_TAG) .
|
|
@echo
|
|
@docker image inspect $(REDIS_PROVISIONER_IMAGE) --format 'built {{.RepoTags}} {{.Size}} bytes'
|
|
|
|
# The proxy that turns a route grant into traffic reaching a workload.
|
|
PROXY_IMAGE ?= mesh-route-proxy:$(VERSION)
|
|
PROXY_DEV_TAG ?= mesh-route-proxy:development
|
|
|
|
proxy-image:
|
|
docker build -f examples/route-proxy/Dockerfile -t $(PROXY_IMAGE) -t $(PROXY_DEV_TAG) .
|
|
@echo
|
|
@docker image inspect $(PROXY_IMAGE) --format 'built {{.RepoTags}} {{.Size}} bytes'
|
|
|
|
# The whole gate. Raises a database, runs everything against it, and takes it down again --
|
|
# including when the tests fail, which is why the teardown is not conditional.
|
|
#
|
|
# **One package at a time (-p 1), and it is not about speed.** The live tests reach one bus, and on
|
|
# it they assert, read and remove the mesh's own objects -- streams and consumers with fixed names,
|
|
# because those names are the mesh's and a test cannot choose others. Two packages doing that at once
|
|
# is one deleting a consumer the other is reading through, and the failure lands in whichever test
|
|
# was reading, as "no response from stream". That reads as a bug in the code under test.
|
|
check: fmt vet postgres
|
|
@go test -p 1 ./... ; status=$$? ; $(MAKE) postgres-stop ; exit $$status
|
|
|
|
# Without a database the live tests skip rather than fail, so this is the honest subset and not
|
|
# the gate. Serialised for the same reason check is: a bus may be configured even when a store is not.
|
|
test:
|
|
go test -p 1 ./...
|
|
|
|
vet:
|
|
go vet ./...
|
|
|
|
fmt:
|
|
@unformatted=$$(gofmt -l . 2>/dev/null) ; \
|
|
if [ -n "$$unformatted" ] ; then echo "not gofmt'd:" ; echo "$$unformatted" ; exit 1 ; fi
|
|
|
|
postgres:
|
|
@docker rm -f $(PG_CONTAINER) >/dev/null 2>&1 || true
|
|
@docker run -d --name $(PG_CONTAINER) -e POSTGRES_PASSWORD=check \
|
|
-p 127.0.0.1:$(PG_PORT):5432 $(PG_IMAGE) >/dev/null
|
|
@printf 'waiting for postgres'
|
|
@for i in $$(seq 1 60) ; do \
|
|
if docker exec $(PG_CONTAINER) pg_isready -U postgres >/dev/null 2>&1 ; then \
|
|
echo ' — ready' ; exit 0 ; fi ; \
|
|
printf '.' ; sleep 1 ; \
|
|
done ; \
|
|
echo ' — never came up' ; docker logs $(PG_CONTAINER) | tail -20 ; exit 1
|
|
|
|
postgres-stop:
|
|
@docker rm -f $(PG_CONTAINER) >/dev/null 2>&1 || true
|
|
|
|
clean:
|
|
rm -rf build/
|