mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-07 20:47:46 +01:00
985706cc32b27b04e7cce1e6fcd76d6944c1bc4e
414
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d0b5529a2d |
S3 Vectors: weed-vector, weed-worker and the CLI write tables to the filers and volume servers (#12470)
* sw-client: the filer and volume client library The client the Enterprise repository's VFS and vector engine use, with the same API: filer gRPC for entries, listings, volume locations, assignments and remote caching; volume HTTP, plain or TLS, for needle bytes; chunk resolution for sparse, overlapping and manifest chunks; a location cache; and the write side. Enterprise-only protocol fields (license, conditional mutations, hard links, KV listing) are left out. Compiled against weed/pb/filer.proto like the other worker crates. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-lance-worker: reads straight from the filers and volume servers direct_read::DirectReads wraps the object store Lance opens so that entries, listings and bytes come from the filers (gRPC) and the volume servers (HTTP, signed with the read JWT), whatever the entry holds: inline content, chunk manifests, sparse or overlapping chunks, the filer's volume encryption, the gateway's SSE-S3 (sse.rs, with the KEK from security.toml or the filer) and objects tiered to remote storage. Writes still go to the inner store. The code and its tests are those of the Enterprise repository's engine. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-vector: read tables from the filers and volume servers The engine asks the masters for the filers (and follows the list), takes jwt.signing.read.key, grpc.client, https.client and the SSE-S3 keys from security.toml, and opens every table with DirectReads innermost and the read cache outside it. --masters is required: the gateway finds the engine through them and the engine finds the filers. Writes still go to the gateway. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * sse: chunks out of a manifest keep their own SSE-S3 keys A chunk expanded from a manifest carries its id as fid, so keying the per-chunk key cache by file_id gave every such chunk the first one's key and IV. Keyed by the chunk's id however it is carried. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct reads: a silent filer is passed over like an unreachable one Each filer attempt is bounded, so a filer that accepts the connection and never answers does not hold the read or the startup lookup. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * sw-client: carry the assignment's fsync to the volume upload The filer's storage rule may want a needle on disk before the volume answers; the assignment says so and the upload now asks for it, as the Go uploader does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-vector: argument tests pass the required masters; connect timeout for filers Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct reads: a failed read is tried once more against fresh locations A fetch that fails forgets the volume locations it was made against; the request now looks them up again and retries instead of failing on a volume that moved. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * sw-client: an exclusive create that finds its entry, and needle deletion create_entry marks ENTRY_ALREADY_EXISTS with EntryExists, so a caller can tell a lost conditional create from a failure; the transport deletes a needle whose entry was never created. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * sse: encode chunks as the gateway stores them EntryEncoder writes SSE-S3 under a fresh object key wrapped by the KEK, serialized with the IV, key id and commitment the gateway's reader expects, each chunk under the IV at its offset; and the filer's volume encryption under a per-chunk key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct storage: writes, deletes and table removal without the gateway A put uploads its chunks to the volume servers the filer assigns, at the filer's chunk size, encoded as the bucket and cluster ask, and creates the entry the gateway would: md5, mime, size, the ETag attribute, the SSE-S3 attributes. A conditional create is the filer's exclusive create, so Lance's manifest commit conflicts the same way; the needles of a create that failed are discarded. A multipart upload turns parts into chunks and derives its ETag from them. Deletes remove the entry with its data, and remove_table a table directory. Copy and rename, which Lance does not use on S3, are not offered. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector.proto: Table says whether its bucket encrypts with SSE-S3 The engine writes the table's files itself, so it needs what the S3 write path knew: whether the bucket declares SSE-S3. storage_options go: the engine reaches no S3 store. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3vectors: send the engine the bucket's SSE-S3 setting The rule the S3 write path applies to a Lance bucket's files, factored out of DeclaredBucketEncryption, decides it from the vector bucket's record. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-vector: every table operation goes to the filers and volume servers Opens, index creation and drops all go through the direct store, so the engine has no S3 client of its own left to protect: --storage-option, the endpoint pin and the request-option allowlist go, and lance's own S3 store is built unsigned and never dialed. The bucket's SSE-S3 setting comes with the table; -encryptVolumeData encrypts chunks for the volume servers as the gateway's flag does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3vectors tests: the engine takes no credentials Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3vectors e2e: the engine writes what the gateway would have Every leg checks that the entries the engine wrote carry the gateway's ETag attribute, the hardened leg that their chunks are encrypted for the volume servers, and a new test that an SSE-S3 bucket's files are written under keys the gateway unwraps when it serves them over S3. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * cluster plumbing moves to the worker crate for the engine, worker and CLI security.toml, the gRPC TLS connector and acceptor, the master dialer, the filer discovery and the storage connection built from them now live in weed-lance-worker (cluster, security, grpc_tls), where the worker and the CLI can use them too. The server crate re-exports security and tls under their old paths. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-worker, weed-vector-cli: tables through the filers and volume servers dataset::Storage replaces the S3 credentials every job and the CLI carried: a table is opened and written through the cluster's storage, encrypted as its bucket declares (read from the bucket's record on the filer), and dropped there. Both take --masters and security.toml like the engine; --access-key and --secret-key go, and with them the catalog's x-api-key credential, which the cluster token replaces. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * lance catalog: the cluster token alone authenticates a cluster process The engine, the maintenance worker and the CLI hold the cluster's filer signing key and reach a table's files themselves. A request presenting the cluster token and no other credential is theirs: it is authenticated by the token, runs through the trusted manager, and is vended no S3 credentials. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * worker chart, README and test design follow the worker's new arguments Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3vectors tests: the worker and the CLI reach the cluster through the masters The cluster gets a filer signing key, which the catalog authenticates the worker and the CLI by; both take --masters in place of S3 keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct storage: encrypted chunks each start their own CTR stream The gateway encrypts a multipart part from the IV derived for its offset, from position zero; the encoder encrypted from the exact byte offset of the object's stream instead, which agreed only at block boundaries. A part of 3,000 bytes read back wrong; now tested. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct storage: keep what a lost reply saved, discard what a failed put left A create the filer saved but did not answer, or an exclusive create retried onto this very upload, keeps its chunks and counts as written; the needles of a put that failed midway are discarded. The report prints reads as reads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-worker: the security.toml it is given also holds its signing key Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * worker chart: the Lance worker mounts grpc.client's certificate Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct reads: the filer deadline bounds a listing page, not the listing Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-vector: security.toml booleans read as viper reads them Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-vector: a filer the master lists badly does not hide the others Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * weed-vector: register with the masters only once ready to serve Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * sse: every chunk after the first is written under a key of its own Chunks of one object shared a key with IVs derived from their offsets, so two chunks meeting inside a cipher block shared keystream. The first chunk stays under the object's key and IV, which is how the gateway reads an object of one chunk; every other chunk gets a fresh key and IV, as the gateway's multipart parts have and as it reads an object of several. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * worker chart: the Lance worker is given the filer group Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3vectors e2e: the gateway serves engine-written SSE-S3 files of one chunk and of several Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * sw-client: the HTTP transport asks the volume to fsync when the assignment does write_synced fell back to the trait default, which refuses fsync, so every upload under a fsync storage rule failed before it was sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * sw-client: https.client.ca alone vouches for volume servers With a CA configured, reqwest still trusted the system roots next to it; weed processes trust only the configured pool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * sw-client: an empty volume range is read without a request A zero length underflowed the Range end, and a range past u64::MAX overflowed it; the first now returns no bytes, the second an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * sw-client: a whole-needle answer to a ranged read is cut to the range A server or proxy that ignores Range answers 200 with the whole needle; the read returned its first bytes in place of the ones asked for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * sw-client: a read size past u64::MAX reads to the end of the file offset + size overflowed before the clamp to the file size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * sw-client: an SSE-encrypted manifest is refused without a decoder Only filer-encrypted manifests were; an SSE one went to the protobuf decoder as ciphertext. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * sw-client: an empty write leaves the staged file size alone A zero-byte write past the end grew the size to its offset, as a zero-byte pwrite does not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * sw-client: the chunk cache keeps no empty chunks They add no bytes, so nothing evicted them and the map grew with every distinct empty needle, even at capacity zero. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * sw-client: flushing with a zero chunk size is an error, not a panic Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-vector: an empty master list fails at startup "" split into one empty address that passed the check, and the engine then waited two minutes for a filer before a generic error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * lance cluster: an empty master list fails at startup Cluster::new took "" as one master, so weed-vector, weed-worker and the CLI waited two minutes for a filer before a generic error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * direct storage: a failed create keeps its chunks when the filer cannot be asked The lookup after a failed create is what tells a lost reply from a refusal; when it fails too, the entry may be saved, so its chunks stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-vector: a gateway-described table takes the shared unsigned store options Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * vector.proto: give sse_s3 a new field number and reserve storage_options Field 4 held the storage_options map on master; a gateway from that build would send a length-delimited value the engine cannot decode as a bool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-vector-cli: open s3:// URIs on the cluster's storage A table named by its s3:// location skipped the store wrapper and opened Lance's own S3 store, which has no credentials since the fallback flags were dropped. It now opens like a namespace table and needs --masters; local paths keep the plain builder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * vector: an index id is plain path components, and drop trusts describe() `resolved()` now refuses an id whose part is empty, `.`, `..` or holds a `/`, since the colocated path would land elsewhere on the filer; and `drop_index` no longer repeats the colocation check for the location describe() already enforced for the configured schemes, which made a drop on a memory:// table fail in the scheme test configuration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: a file delete takes only its entry; a table path is plain names The store's delete of one file no longer asks the filer for a recursive delete, so a path that names a directory does not take its content with it, and `remove_table` refuses a location whose bucket or path component is empty, `.`, `..` or holds a separator, which the filer would resolve outside the table. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: chunks are assigned to the bucket's collection The filer assigns a chunk's volume from the collection the caller names; the gateway names the bucket, prefixed by the filer group when the cluster has one, so a plain assign landed the engine's chunks in the default collection. The store now asks for `<bucket>` or `<group>_<bucket>`, with the group taken from the cluster connection. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: a put's content type becomes the entry's mime Lance sets no content type today, so every entry was written as application/octet-stream; a put that does carry one now creates the entry with it, as the gateway does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: an upload whose create went unanswered is done with A create the filer took and never answered (a timeout, or a transport that reports the filer unavailable) may still be applied, so its chunks are kept, as they already were for a create whose reply was lost. The upload then records that it is done: a second completion is refused instead of creating an empty entry, and an abort discards nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: an upload dropped unfinished discards its chunks A request cancelled mid-write drops its multipart upload with neither complete nor abort called, leaving its needles on the volume servers. Dropping an upload that still holds chunks now discards them on the runtime; a completed upload, or one whose completion is in doubt, holds none. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3: a cluster process registers and deregisters in the vector namespace The Lance catalog ran a cluster-token caller through the trusted manager for every operation but the vector-namespace write check of register and deregister, which still used the caller-checked manager and answered 403 for an index of a LANCE bucket. One `manager(r)` now picks the manager for all three. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: the worker's flags take two dashes The worker parses its flags with clap and, unlike the engine, does not accept Go-style single-dash long flags, so the harness's hardened worker leg and the README's `-encryptVolumeData` did not start it. The README's commands also gain the `--masters` the worker now requires. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: the CLI takes --encryptVolumeData An index build or optimize from the CLI wrote its chunks unencrypted for the volume servers unless the filer itself asked for the cipher; the CLI now takes the flag the engine and the worker have. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: a bucket's encryption is read when a table is opened The setting was cached for a minute, so a bucket recreated with SSE-S3 within that minute had its first tables written plain, and a record that could not be decoded was read as "no encryption". A table open now reads the bucket's record from the filer, once per open, and an unreadable record is an error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: a removed table's files leave the object cache `remove_table` asked the cache to forget everything under the table, but the cache was built without invalidation closures, so the call was refused and its error dropped: a data file read before the drop was still served afterwards. The cache now supports the invalidation, and a refusal is an error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: an abort keeps the chunks it could not discard `discard_all` stopped at the first chunk a volume server would not give up and dropped the rest from the upload's state, so a retried abort found nothing to do and the needles stayed. Every chunk is now attempted, the ones left are kept for the next abort or the drop, and the upload can no longer be completed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: a chunk write that fails gives its needle back When the volume server took the chunk but its answer was lost, the needle stayed under a file id nothing would ever reference. The assignment is known before the write, so a failed write now discards it, best effort, before the error is returned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * vector: the live worker tests require the cluster's masters With WEED_LANCE_NAMESPACE set and WEED_MASTERS not, the tests seeded their tables through lance's own S3 store, which reaches nothing, and failed before a job ran. The missing variable is now named outright. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct storage: only the filer's missing-entry error means not found Matching any "not found" text read failures such as an unresolved remote storage as a missing entry, so a delete or a listing reported success over data that was still there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * direct storage test: compare the chunks' SSE-S3 IVs Each chunk's metadata wraps its key under a fresh nonce, so the metadata differed even if the key and IV were reused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * direct storage: a filer passed over unanswered keeps the outcome open When one filer timed out on a create and the next refused it, the refusal came back without the no-answer mark, so the put discarded chunks the first filer may still reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * lance test: start every concurrent writer before awaiting them The lazy map spawned each writer only after the previous one finished, so the commits never overlapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * direct storage: count failed uploads and deletes The storage report counted failed reads and lookups but not a volume upload, an object delete or a table removal that failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * S3 Vectors tests: run weed-vector-cli as the cluster's processes run The CLI ran without the fixture's environment and outside its directory, so under VECTOR_READ_JWT it could not read the volumes and under VECTOR_TLS it found no security.toml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * direct storage: a filer that fell away leaves the outcome open too An unavailable error (connection reset, deadline, transport failure) can arrive after the filer took the request, so a refusal from the next filer no longer settles a create the first one may still apply: the chunks are kept, as after a timeout. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct storage: a chunk write abandoned in flight gives its needle back The needle is assigned before the write, so a request cancelled while the write is in flight left it on the volume server with nothing to name it. `Source::upload` is now `assign` and `write`, and the upload holds the assignment across the write in a guard: a write that fails discards it and one abandoned discards it on the runtime, while a write that returns hands it to the entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * lance tests: sign for the namespace as the worker does The live tests reached the namespace anonymously once the S3 keys went, and an anonymous caller lists no tables, so detection proposed nothing. They now take the cluster key from where the worker reads it (WEED_JWT_FILER_SIGNING_KEY or security.toml) and log at info, so a table a handler skips says why in the harness output. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3tables lifecycle test: run the Lance worker against the cluster The worker leg of TestLanceTableLifecycle still handed the Rust tests S3 credentials and no masters. The mini cluster now has a cluster key, and the tests get WEED_MASTERS and that key; passes with WEED_LANCE_MAINTENANCE=worker (3 fragments became 1, 4 versions removed). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * direct storage: name no collection outside a filer group, as the gateway Without a filer group the gateway leaves the collection to the filer, so a filer.conf rule for the bucket's path picks it; naming the bucket overrode that rule, and the chunks landed where a bucket delete does not look. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * direct storage: a read reports the entry's content type A put now stores its ContentType as the entry's mime, but get_opts returned no attributes, so the type did not come back on a read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
9b5f7bf65b |
weed-worker: find jwt.filer_signing.key where the Go worker does, and let the chart deliver it (#12373)
* seaweed-common: share the security.toml search path with the volume server * weed-worker: read jwt.filer_signing from security.toml and WEED_JWT_* like the Go worker * helm: give the worker-lance container security.toml and the extra environment * weed-worker: treat an empty signing key in the environment as unset Viper ignores an empty environment value, so the Go worker falls back to the security.toml key when WEED_JWT_FILER_SIGNING_KEY is blank. The Rust worker took the blank value and connected with no token. An empty --filer-signing-key / WEED_FILER_SIGNING_KEY also falls through to the security.toml lookup now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-worker: read a quoted expires_after_seconds like viper The Go worker reads jwt.filer_signing.expires_after_seconds with GetInt, which also accepts a quoted number. The Rust worker dropped a quoted value and minted tokens with no expiry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-worker: warn when security.toml cannot be read or parsed Viper logs a warning when an optional config file is broken. The Rust worker skipped it silently, so a typo looked like an install with no signing key. Log the path and the parse message, without the quoted line, which can hold a key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * helm: keep POD_NAME and LANCE_NAMESPACE_URL out of worker-lance's extra environment A duplicate name renders after the chart's entry and wins, silently changing the Lance worker's --id or --namespace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-worker: list /usr/local/etc/seaweedfs in the security.toml search path Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-worker: an explicit --filer-signing-key-expires-after-seconds wins The flag was silently ignored whenever the key was discovered, from WEED_JWT_FILER_SIGNING_KEY as well as from security.toml. Use it when given, else the expiry configured beside the discovered key, else zero. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-worker: test where filer_signing looks for security.toml Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-worker: read a float expires_after_seconds as viper's GetInt does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * weed-worker: cap the minted exp so a huge expiry cannot overflow Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
984eac6a79 | 4.52 | ||
|
|
468aabc962 |
helm: add commonLabels to the metadata of every object (#12257)
* helm: add commonLabels to the metadata of every object
The chart lets users label pods (podLabels, <component>.podLabels) and
annotate workloads, but offers no way to label the objects themselves.
Backup, ownership and cost policies usually select StatefulSets,
Deployments, Services, PVCs or Secrets by label, and umbrella charts
that set such labels on all their workloads have to patch this chart
to do it.
commonLabels is added to the metadata labels of every object the chart
renders, through one seaweedfs.commonLabels helper. Values may hold
templates, and the whole value may be a template string rendering to a
map, so a parent chart can pass its own labels down. The chart's
app.kubernetes.io/{name,instance,component,managed-by} and helm.sh/chart
keys are dropped, and keys an object already sets (ingress.labels,
httpRoute.labels, monitoring.additionalLabels, grafana_dashboard) win,
so selectors never change and no key renders twice.
Pod templates are left alone (podLabels covers them, and a label change
would roll the pods), as are volumeClaimTemplates, which cannot change
on an existing StatefulSet. checksum/master-config now hashes the master
ConfigMap as rendered without commonLabels, so labels do not roll the
masters either; its value is unchanged.
With commonLabels empty, the default, every template renders exactly as
before. Adds a helm_ci check that labels a release with most components
on and asserts every object carries the labels and nothing else changes.
* helm: do not reject a commonLabels key literally named Error
fromYaml reports parse failures as an {"Error": msg} map, so checking
for that key also matches a user-supplied label of the same name. Only
treat Error as a parse failure when the raw value was not already a
map, so {Error: tracked} renders while non-map input still fails.
* helm: exclude the filer monitoring label from commonLabels
filer.metricsPort adds a hardcoded monitoring: "true" label, which
collided with a monitoring key in commonLabels and rendered a duplicate
map key. Exclude it while metricsPort is set so the object's label wins,
and keep the user's label when it is not.
* helm: accept a templated commonLabels string with an Error key
fromYaml reports a parse failure as a one-key map {Error: "error ..."}, so a
templated string such as 'Error: x' was taken for a parse error. Only fail
when the value is not a map and fromYaml returned its error shape.
* helm ci: keep ConfigMap data in the commonLabels render comparison
Generated Secrets still need normalizing, but the security ConfigMap was
only non-deterministic because of its default jwt signing key. Disable
that key for this render and compare ConfigMap payloads like everything
else so a commonLabels regression in data is caught.
* helm ci: render the security ConfigMap in the commonLabels comparison
Disabling jwtSigning.volumeWrite alone makes securityConfigEnabled false,
so the ConfigMap was absent from the render instead of compared. Enable
mTLS without the generated key so the object is rendered deterministically
and both its labels and data are covered.
* helm ci: strip only generated-credential secret payloads in the commonLabels comparison
* helm ci: assert the effective non-reserved commonLabels
* helm: render integral numeric commonLabels without exponent
---------
Co-authored-by: jeffWild <20067853+jeffWild@users.noreply.github.com>
Co-authored-by: chrislusf <chrislusf@users.noreply.github.com>
Co-authored-by: Chris Lu <chris.lu@gmail.com>
|
||
|
|
b7bfbaf9eb |
helm: add namespaceOverride to deploy into another namespace (#12256)
* helm: add namespaceOverride to deploy into another namespace When the chart is a subchart of an umbrella chart, every object lands in the umbrella's release namespace, with no way to give SeaweedFS its own. namespaceOverride, empty by default, now sets the namespace of every object through a new seaweedfs.namespace helper, which falls back to the release namespace. All uses of .Release.Namespace go through it, so the in-cluster addresses, certificate SANs, ClusterRoleBinding subjects, HTTPRoute backendRefs and lookups follow the override too. The self-signed Issuer, the bucket hook Job and the volume resize hook objects, which had no metadata.namespace, now set it. With namespaceOverride unset the default render is unchanged. Adds a helm_ci check that renders with an override and asserts nothing is left in the release namespace. * ci: cover the bucket hook Job in the all-in-one namespace test The all-in-one render set s3.createBuckets, which bucketHookEnabled ignores in allInOne mode, so the Job never rendered and the namespace check could not see it. Use allInOne.s3.createBuckets and assert the Job is present. * helm: fail on an invalid namespaceOverride instead of reshaping it Truncating and trimming could silently map an override onto a different existing namespace and deploy into it. Validate against the DNS-1123 namespace rules and fail the render instead. * helm: bound the master data volume name at 63 characters data-<namespace> can exceed the Kubernetes name limit when the release or override namespace is near 63 characters; truncate the composed name and trim a trailing hyphen like the other name helpers. * helm: quote the rendered namespace in YAML fields * helm: warn that changing namespaceOverride strands claims and data * helm: say namespaceOverride only moves namespaced objects * helm: retrigger the PR head sync (no changes) * helm: warn that secrets and hook resources do not follow namespaceOverride * helm: warn that hostPath data does not follow namespaceOverride --------- Co-authored-by: jeffWild <20067853+jeffWild@users.noreply.github.com> Co-authored-by: chrislusf <chrislusf@users.noreply.github.com> |
||
|
|
e9f0d3ad2c |
helm: set resources on the worker-lance container (#12240)
* helm: set resources on the worker-lance container Signed-off-by: AkshatIIITD <akshatvikramsingh@gmail.com> * helm: Updated worker-deployment according to bot input Signed-off-by: AkshatIIITD <akshatvikramsingh@gmail.com> * helm: drop trailing whitespace in values.yaml --------- Signed-off-by: AkshatIIITD <akshatvikramsingh@gmail.com> Co-authored-by: chrislusf <chrislusf@users.noreply.github.com> |
||
|
|
665659097d |
helm: mount existingClaim data and logs volumes for filer and master (#12234)
filer.data, filer.logs and master.logs accept type existingClaim and render the pod volume for it, but the volumeMount (and, for logs, -logdir) only covered hostPath, persistentVolumeClaim and emptyDir. With filer.data.type=existingClaim the claim was attached to the pod but never mounted, so the filer kept its metadata store on the container's own filesystem and lost it on every restart. admin and worker already include existingClaim in the same conditions. Adds a helm_ci check that renders both StatefulSets with existingClaim and asserts the mounts and -logdir. Co-authored-by: Pat White <5015863+patwhite@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
89cc910a86 |
helm: render the filer's mysql credentials once when the user sets them (#12139)
* helm: render the filer's mysql credentials once when the user sets them With the MySQL filer store, the filer reads WEED_MYSQL_USERNAME and WEED_MYSQL_PASSWORD from the chart's db secret. Setting either one in extraEnvironmentVars or secretExtraEnvironmentVars, as helm_ci.yml does for the password, added a second entry with the same name: Helm 4 rejects the install (env: duplicate entries for key), and Helm 3 deploys with a warning that one definition may be dropped on apply. The db secret's entry is now left out for a key the user sets, so each key is rendered once and the user's value wins, the rule #8488 applied to a key set in both global and filer extraEnvironmentVars. Without an override the filer StatefulSet is unchanged. * helm: render the selected filer mysql credential ahead of the merged vars A credential the user sets now renders in the early position the db secret's entries used to occupy, and is excluded from the merged and secret loops. Kubernetes resolves a $(VAR) reference only against names declared earlier, so a later entry such as WEED_MYSQL_DSN can again reference the active credential. Secret-backed values win over plain ones; the db secret's entry remains the fallback. * helm: let a secret-backed filer env key replace the plain one secretExtraEnvironmentVars now wins over extraEnvironmentVars for the same name, rendering once at the merged key's sorted position instead of twice. The values.yaml WEED_MYSQL_* defaults no longer duplicate when overridden through the secret map - e.g. enabling the store via a secret WEED_MYSQL_ENABLED also rendered the "false" default before, which Helm 4's server-side apply rejects as a duplicate. * helm: keep a credential that references another env var in sorted position A user-supplied credential whose value itself holds a $(...) reference stays in the merged vars' sorted order so its own references still resolve; only reference-free values, valueFrom entries, and the db-secret defaults move to the early slot. * helm: render vars a filer mysql credential references ahead of it Hoisting the selected WEED_MYSQL_USERNAME/PASSWORD entries above the merged extra vars let WEED_MYSQL_DSN-style $(...) references resolve, but it broke the opposite direction: a user-set credential such as WEED_MYSQL_USERNAME: "$(DB_USER)" was emitted before DB_USER, so Kubernetes left the reference literal. Pull every merged or secret var a credential value transitively references out of the sorted set and emit them first, dependencies before dependents, so both directions resolve. * helm: emit filer mysql credentials after the chart's own env vars The dependency-ordered credential block rendered before SEAWEEDFS_FULLNAME and the s3 credentials, so a user-supplied WEED_MYSQL_USERNAME could not reference them via $(...). Move the block below those fixed entries so references in either direction resolve. * helm: bound filer mysql credential ordering passes by var count Chains deeper than the fixed eight passes forced a credential out ahead of the vars it references. Cap discovery at the number of merged/secret vars and emit passes at the ordered list length, so any resolvable chain finishes before the last-pass fallback emits cyclic leftovers. --------- Co-authored-by: chrislusf <chrislusf@users.noreply.github.com> |
||
|
|
3a0af308ce |
helm: keep the filer db credentials across uninstall and install (#12138)
* helm: keep the filer db credentials across uninstall and install The db secret is kept on uninstall (`helm.sh/resource-policy: keep`), but it is a pre-install hook, and Helm deletes an existing hook resource before creating it again. The next install therefore wrote a new random password and reset the user to YourSWUser, so the filer could no longer log in to a database set up with the old ones, including credentials edited into the kept Secret. The template now reads the existing Secret with lookup and reuses its user and password; a first install still generates them as before. A kind step in helm_ci.yml installs, edits the Secret, uninstalls, installs again and checks both values. * helm: README, the db secret is kept and reused across a reinstall * helm: make the db secret hook-delete-policy explicit before-hook-creation is Helm's default for hook resources, but spell it out so the reuse contract is obvious: lookup reads the kept Secret at render time, the old copy is deleted before the hook recreates it, and the stored credentials carry over. --------- Co-authored-by: Chris Lu <chris.lu@gmail.com> |
||
|
|
ef16b4ed02 |
helm: render numbers and booleans in extraEnvironmentVars as values (#12137)
Only a string rendered as `value`; anything else went to `valueFrom`. An unquoted number or boolean in a values file or `--set`, such as `WEED_MYSQL_PORT: 3306` or `WEED_MYSQL_ENABLED=true`, rendered as `valueFrom: 3306` and the API server rejected the workload. A map still renders as `valueFrom`, a null still renders an empty variable, and strings are unchanged. Numbers go through toJson because values files decode them as float64, and quote would print 1000000 as 1e+06. |
||
|
|
153ab668c9 | 4.51 | ||
|
|
ec8f979350 | 4.50 | ||
|
|
f9dac64374 |
helm: remove filer.redirectOnRead, which weed filer does not accept (#12040)
weed filer lost -redirectOnRead in |
||
|
|
c4002fafc7 |
helm: fix bucket hook treating an existing bucket as missing (#11982)
* helm: fix bucket hook treating an existing bucket as missing The skip check pipes awk into `grep -q`, which exits on the first match and can leave awk writing into a closed pipe. awk then dies of SIGPIPE and `set -o pipefail` makes the pipeline report that, so a bucket that exists reads as missing: the hook runs `s3.bucket.create`, it errors with "already exists", and `sh -ec` aborts the hook - failing the release. Match in a single awk instead, which reads to EOF and cannot be killed by a closing reader. * helm: force string compare in bucket hook existence check --------- Co-authored-by: Chris Lu <chris.lu@gmail.com> |
||
|
|
b97ed0f101 | 4.49 | ||
|
|
8ac5133abe |
admin: default to port 12535, keep 23646 as a best-effort compat listener (#11816)
* admin: default to port 12535, keep 23646 as a best-effort compat listener The default admin HTTP port 23646 derives a worker gRPC port of 33646 (port + GrpcPortOffset), inside the Linux default ephemeral range (32768+). One of our own startup dials can claim 33646 before the admin binds it, killing startup. This is the same failure #10209 fixed for the test port allocator, still reachable via the mini default (#10838) and via standalone `weed admin` defaults. Move the default admin port to 12535 (gRPC 22535) everywhere, keeping the http+10000 convention so worker -admin derivation, the Rust worker and host:http.grpc addressing keep working: - weed admin -port, weed mini -admin.port - weed worker -admin, the plugin worker fallback, Rust worker and weed-lance-worker defaults - help text, README, helm values, docker compose, tests So old workers, bookmarks and scripts keep working, admin also serves the same handler and the worker gRPC server on the previous default (-port.legacy / -admin.port.legacy, default 23646, 0 disables). The legacy gRPC port binds best-effort: it lives in the ephemeral range, so a failed bind only warns instead of reintroducing the startup failure. weed mini reserves it early like the primary port and keeps it out of its own port relocation. grpc.Server tracks every listener handed to Serve, so the compat listener stops with the primary. Also warn when an explicitly configured admin port puts the derived gRPC port at or above the ephemeral floor, the other fix suggested in the issue. Supersedes the pinned-grpc approach in #11814: pinning -admin.port.grpc off the +10000 convention breaks external workers that derive the admin gRPC port from the HTTP address. ClickHouse/ClickHouse#115512 pins -admin.port on their side; this makes that pin unnecessary. Fixes #10838 * admin: let services win the legacy ports, close compat listener on failure Review findings on the compat listeners: - Do not put the legacy ports in mini's reservedPorts: an explicit -s3.port=23646 or -master.port.grpc=33646 must be honored, not rejected or stolen by the early reservation. Mini now checks the finalized port set and skips only the conflicting compat listener, so HTTP and gRPC compat degrade independently and the service keeps its port. - Also skip the legacy HTTP listener when a mini service owns the port: a wildcard-bound service can still leave loopback bindable, which would let the compat listener shadow it for loopback clients. - Close legacyServer's raw listener in the deferred cleanup: Serve may not have registered it yet when startAdminServer returns early. - Helm: add admin.legacyPort (default 23646) and expose the legacy http/grpc ports through the admin Service, container ports and NetworkPolicy so older workers resolved via the Service keep working. * helm: only pass -port.legacy when it differs from the binary default ct install deploys the released image with the new chart, and 4.48 does not know -port.legacy, so the admin pod crash-loops. Omitting the flag at the default value is behavior-identical on new binaries (their default is also 23646) and keeps older releases installable; legacyPort: 0 or a custom port still renders the flag. |
||
|
|
bc29ffbfbd |
helm: apply placement values to the chart's hook Jobs (#11795)
* helm: honor placement values in the bucket creation hook The post-install/post-upgrade bucket Job ignored nodeSelector, tolerations and affinity, so it could land on nodes the operator kept clear, or fail to schedule at all on a fully tainted cluster. The hook now accepts nodeSelector, tolerations and affinity under each createBucketsHook (filer.s3, s3, allInOne.s3), read from the same values section that supplies createBuckets. Unset nodeSelector and tolerations fall back to the workload the hook already borrows its security context from; affinity is explicit-only since the anti-affinity rules of a long-lived server do not suit a one-off Job. * helm: honor placement values in the volume resize hook The pre-install/pre-upgrade resize Job inherited nothing from the volume server, so on a cluster whose storage nodes are all tainted the Job could not be scheduled and the upgrade timed out. The hook now falls back to the volume server's nodeSelector and tolerations, and accepts explicit nodeSelector, tolerations and affinity under volume.resizeHook. Affinity stays explicit-only: the default podAntiAffinity against component=volume would keep the Job off every node already running a volume pod, which is most of them during an upgrade. Placement strings are rendered through tpl with $volumeName bound, same as the StatefulSet. |
||
|
|
f11cecc697 |
helm: keep the S3 config checksum stable across the first upgrade (fixes #11719) (#11720)
* helm: keep the S3 config checksum stable across the first upgrade (fixes #11719) checksum/s3config hashed the rendered s3-secret.yaml. That template keeps its generated S3 keys with lookup, so rendering it again for the checksum drew new keys whenever lookup found nothing: on install the filer, S3 and all-in-one pods each carried a checksum of a different set of keys than the Secret got, and the first upgrade with unchanged values corrected it and restarted them. Hash the values s3-secret.yaml is built from instead: the enable and auth switches, existingConfigSecret, credentials, the legacy-secret settings, the audit log configs and the Secret name. Only those, so scaling the S3 gateway or changing its resources does not restart the filer. A change to any of them still restarts the pods. Keys that the chart generated stay out of the hash; they only change if the Secret is deleted by hand. The new render test fails on the old template (two checksums for one Secret, different on every render) and passes now; it also checks that the checksum follows the credentials and filer.s3.auditLogConfig and ignores s3.replicas and s3.resources. On kind, an upgrade with unchanged values no longer replaces the filer pod. * helm: check the S3 checksum per workload, all-in-one included Each workload's checksum/s3config is now read on its own (--show-only), so a workload that loses the annotation fails the test instead of being hidden by sort -u, and the all-in-one deployment gets the same checks. Verified against two broken copies: one without the filer annotation, one with the old all-in-one checksum; the test fails on both. The helper comment now says what happens when the Secret is deleted to rotate generated keys. * ci(helm): fail the S3 checksum test on a missing or unrendered checksum A failed helm template or a render without the annotation produced an empty checksum, and the inequality assertions then passed against it. s3_checksum now returns non-zero unless it found a checksum, and the changed-value renders are assigned before comparing so failures cannot hide inside test. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Ege Zambelli <egezamb@users.noreply.github.com> Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
68af030e38 |
helm: add Gateway API HTTPRoute for master, volume, filer, s3 and admin (#11625)
* helm: add Gateway API HTTPRoute for master, volume, filer, s3 and admin Each component that offers an Ingress can now be exposed through a Gateway API HTTPRoute instead, via <component>.httpRoute. It is disabled by default, so existing renders are unchanged. parentRefs, hostnames, labels and annotations pass through as given. Each rule may set matches, filters, timeouts and backendRefs, and a rule without backendRefs routes to the component's own service and port, following all-in-one mode the same way the Ingress templates do. An empty rules list yields a single rule sending all traffic there. Signed-off-by: younsl <cysl@kakao.com> * helm: fix HTTPRoute backend selection for S3 on filer and all-in-one - s3: with S3 on the filer the route targets filer.s3.port, the port the s3 Service exposes. The all-in-one Service is chosen only when allInOne.s3 is enabled, so a standalone S3 next to all-in-one routes to the s3 Service. - master: render in all-in-one mode and route to the all-in-one Service, like the volume and filer routes. - Name routes with seaweedfs.componentName, keeping them within 63 characters like the Services they point to. - CI: cover the filer S3 port and the all-in-one master route. Signed-off-by: younsl <cysl@kakao.com> * helm: document the Gateway API HTTPRoute values in the chart README Signed-off-by: younsl <cysl@kakao.com> * helm: link the Gateway API docs from the chart README Signed-off-by: younsl <cysl@kakao.com> * helm: scope the filer S3 port note in the chart README Signed-off-by: younsl <cysl@kakao.com> --------- Signed-off-by: younsl <cysl@kakao.com> |
||
|
|
199d78539a |
fix(cosi): add get/list/watch on bucketclasses to enable static provi… (#11620)
* fix(cosi): add get/list/watch on bucketclasses to enable static provisioning * fix(cosi): bind provisioner deployment to the chart-created service account The deployment referenced a release-prefixed service account name while the chart creates and binds "seaweedfs-objectstorage-provisioner", so the RBAC grants never reached the provisioner pod. --------- Co-authored-by: Jonas Onshuus <jons@dips.no> Co-authored-by: Chris Lu <chris.lu@gmail.com> |
||
|
|
b14dd1cee4 |
helm: drop fromToml dependency in security-configmap.yaml (fixes #11611) (#11614)
* helm: drop fromToml dependency in security-configmap.yaml (fixes #11611) fromToml was added to Helm in v3.17.0 (helm/helm#12026, merged 2024-09-12, one day after v3.16.0 was cut). This chart declares no minimum Helm version (no Chart.yaml kubeVersion, nothing in the README), and the call in security-configmap.yaml:21 is an unconditional *parse*-time failure on Helm < v3.17.0 - Go's text/template parses a file's entire body before evaluating any {{if}}, so this breaks the chart (any topology, any values) even when securityConfigEnabled is false and the ConfigMap would render nothing. Replaces the fromToml-based dig lookup with a small regex-based helper (seaweedfs.existingTomlKey) that reads the same four "key = ..." JWT signing-key values out of a previously-rendered security.toml, preserving the existing fallback-to-random behavior exactly. Verified: - helm lint (v3.16.3 and v4.3.0): clean - helm template with chart defaults: byte-identical output to the unpatched chart rendered via Helm v4 (which has fromToml) - the disabled/default path is untouched - helm template with security enabled, no prior ConfigMap: identical structure to the unpatched chart (helm v4), modulo the expected random key - Real helm install + helm upgrade round trip (live lookup, since "helm template" never evaluates lookup, even under the original fromToml code): the JWT signing key is identical across both releases - confirms key persistence across upgrades is preserved, not just "renders without erroring" - helm template with chart defaults, Helm v3.16.3: previously failed with a parse error naming fromToml as undefined; now renders successfully Fixes #11611. * helm: harden existingTomlKey against commented key lines and CRLF Addresses two review findings from greptile-apps on PR #11614: - The key-line regex matched the first "key = ..." anywhere in the section block, including a commented-out "# key = ..." line, which would shadow a real active key on a hand-edited or otherwise non-chart-generated ConfigMap. Anchored to line start with the Go regexp multiline flag ((?m)^key...), which a line starting with "#" cannot match. - The section-header match required an exact "]\n", so a ConfigMap with CRLF line endings would fail to match the block at all and regenerate the key instead of reusing it. Changed to "]\r?\n". Also adds a CI test ("Verify JWT signing key persistence across upgrades") exercising all of this end to end with a real helm install -> edit the live ConfigMap -> helm upgrade cycle, matching the existing "Verify SFTP host key secret lifecycle" test's shape: both edge cases are reproduced against a real ConfigMap and asserted on the post-upgrade rendered security.toml. Verified locally (same commands as the new CI step) against a real cluster before pushing. * helm: preserve JWT keys across supported TOML layouts * ci: use setup-python interpreter for JWT upgrade checks * helm: preserve keys under quoted TOML section headers * helm: ignore unrelated quoted TOML section headers |
||
|
|
8ebef03509 |
helm: expose s3 update strategy, lifecycle, and termination grace period (#11602)
The standalone s3 Deployment hardcoded terminationGracePeriodSeconds and offered no way to set a container lifecycle or the Deployment strategy, so operators could not add a preStop delay to drain endpoints before SIGTERM or hold maxUnavailable at 0 during rollouts. Add s3.updateStrategy, s3.lifecycle and s3.terminationGracePeriodSeconds. Defaults render the same manifest as before. |
||
|
|
0d93dec145 | helm: mount TLS certificates in bucket hook (#11589) | ||
|
|
c9ade3f9fd |
helm: compare PVC sizes numerically in the volume resize hook (#11575)
include always returns a string, so gt compared the rendered quantities (e.g. "1.2884901888e+11" vs "6.442450944e+10") lexically. Growing a volume from 60Gi to 100Gi/120Gi or 500Gi to 1Ti emitted no kubectl patch: the StatefulSet was recreated with the new volumeClaimTemplate but the PVC kept its old size. Shrinks such as 120Gi -> 60Gi emitted a patch instead. Pipe both values through float64 before comparing. |
||
|
|
43abe21ffa |
Helm: Add Opt-in Read-Only Root Filesystem Support (#11562)
* security context changes Signed-off-by: Subhadeep Maity <smaity@slb.com> * root file changes Signed-off-by: Subhadeep Maity <smaity@slb.com> * Skip tmp mount when extras provide one; use allInOne context for the bucket hook * Mount tmp for secondary containers; keep user /tmp on the main container --------- Signed-off-by: Subhadeep Maity <smaity@slb.com> Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> Co-authored-by: Subhadeep Maity <smaity@slb.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com> |
||
|
|
7c7834c98e |
Helm: Add Configurable Security Contexts for Chart-Managed Workloads (#11558)
* security context changes Signed-off-by: Subhadeep Maity <smaity@slb.com> * extending examples Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> * updating examples Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> * examples Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> * Update k8s/charts/seaweedfs/values.yaml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update k8s/charts/seaweedfs/values.yaml Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * review comments Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> --------- Signed-off-by: Subhadeep Maity <smaity@slb.com> Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> Co-authored-by: Subhadeep Maity <smaity@slb.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> |
||
|
|
a033ab76e5 | helm: add base dns names to admin cert (#11518) | ||
|
|
530be3e373 | 4.48 | ||
|
|
44ba070d83 |
helm: document admin.allowInsecureBind and drop stale comment
- README and values.yaml now describe the allowInsecureBind escape hatch alongside the non-loopback bind guard - remove a PR reference from the CI test comment |
||
|
|
062238bb5c |
helm: let admin.allowInsecureBind satisfy the admin auth render guard
PR #11185 made `weed admin` refuse to bind a non-loopback address without -adminPassword or mTLS. PR #11228 added -allowInsecureNoAuth as an explicit opt-out for operators who restrict admin access some other way (e.g. a NetworkPolicy plus an authenticating reverse proxy). The chart's render-time guard added by #11236 (admin-statefulset.yaml, seaweedfs.admin.authEnabled) predates -allowInsecureNoAuth and only recognizes password-based auth, so there was no values.yaml path to express that choice: the chart would fail(...) even though the binary itself would start fine with a warning. Add admin.allowInsecureBind (default false) to the seaweedfs.admin.authEnabled helper's checks; when true it renders -allowInsecureNoAuth on the admin command and satisfies the render guard alongside the existing password-based checks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a9ecfeef45 |
helm: roll master pods when master config changes (#11331)
* helm: roll master pods when master config changes The master loads master.toml once at startup (startAdminScripts reads master.maintenance.scripts and sleep_minutes via viper with no config watching), and the master ConfigMap is mounted with subPath, which kubelet never refreshes in a running pod. So a change to .Values.master.config today updates the ConfigMap but running masters keep executing the old configuration until something else restarts them. Add a checksum/config annotation on the master pod template, following the existing checksum/s3config pattern on the filer and s3 pods, so a master config change triggers a rolling restart of the masters. Signed-off-by: Evans Mungai <mbuevans@gmail.com> * Guard against duplicate keys Signed-off-by: Evans Mungai <mbuevans@gmail.com> * Add checksum to deployment as well Signed-off-by: Evans Mungai <mbuevans@gmail.com> * Always ensure the annotation is set Signed-off-by: Evans Mungai <mbuevans@gmail.com> * Update comments Signed-off-by: Evans Mungai <mbuevans@gmail.com> * Soften stance Signed-off-by: Evans Mungai <mbuevans@gmail.com> * helm: merge pod annotations before checksums --------- Signed-off-by: Evans Mungai <mbuevans@gmail.com> Co-authored-by: Chris Lu <chris.lu@gmail.com> |
||
|
|
1f61097d4d |
helm: grant List to the generated read-only S3 identity (#11318)
* helm: grant List to the generated read-only S3 identity The chart's anvReadOnly identity only carried the Read action, so its credentials could GetObject and HeadObject but every ListObjects request was denied: List is a separate action and the identity check is an exact match. Add List so the read-only credentials can list buckets and objects. Writes stay denied. Update the README example to match. Bump the chart to 4.47.1. The chart label is part of the s3 and all-in-one pod templates, so the upgrade rolls the gateways and they reload the identity config, which is only read at startup. Fixes #11317 * helm: roll standalone S3 and all-in-one on s3 config changes Mirror the filer checksum/s3config pod annotation in the standalone S3 and all-in-one deployments so a changed generated S3 secret triggers a rollout during a normal helm upgrade without relying on a chart version bump. --------- Co-authored-by: Chris Lu <chris.lu@gmail.com> |
||
|
|
c507336000 | 4.47 | ||
|
|
fd4fa72289 |
helm: pass -ip to admin so StatefulSet becomes Ready on 4.46 (#11236)
* helm: pass -ip to admin so StatefulSet becomes Ready on 4.46 Since weed admin 4.46 changed its default listen address from all interfaces to loopback (127.0.0.1), the admin StatefulSet template never passed -ip, so the admin server bound to loopback only. The chart's httpGet readiness/liveness probes dial the pod IP, not loopback, so the probes never succeeded and the admin StatefulSet stayed 0/1 forever — breaking upgrades with helm --wait or GitOps controllers. Add an admin.ip value (default "0.0.0.0", restoring the pre-4.46 behaviour) and render it as -ip. A non-loopback bind requires authentication, so fail at render time when admin.ip is non-loopback and neither admin.secret.adminPassword nor admin.secret.existingSecret is set, instead of letting the pod crash-loop. Document the value and add a chart-testing CI values file. Bumps chart to 4.46.1. Fixes #11234 * helm: address review feedback on admin bind validation Align the chart's loopback classification with weed admin's isLoopbackIp (net.ParseIP + IsLoopback): the whole 127.0.0.0/8 range and ::1 are loopback; localhost and wildcard addresses are non-loopback, matching the binary. Previously the exact-string check rejected valid loopback addresses like 127.0.0.2 while permitting localhost (which the binary treats as non-loopback). Recognize WEED_ADMIN_PASSWORD supplied via admin.extraEnvironmentVars / admin.secretExtraEnvironmentVars as authentication, since weed admin picks it up through viper's AutomaticEnv. Previously such deployments were wrongly rejected at render time. Remove [https.admin] mTLS from the validation message and docs: the chart only generates [grpc.admin] (gRPC mTLS), not [https.admin] (HTTP mTLS), so mentioning it as an alternative was misleading. Document that the -ip flag requires SeaweedFS 4.46 or newer, so pinning admin.imageOverride to an older image is not supported with this chart. Extracted the loopback and auth checks into reusable helpers (seaweedfs.admin.isLoopbackIp, seaweedfs.admin.authEnabled) following the existing seaweedfs.filer.mysqlEnabled pattern. * helm: tighten loopback classification to reject malformed 127.x addresses Use regexMatch instead of hasPrefix for the IPv4 loopback check so malformed values like "127.not-an-ip" are not accepted as loopback (net.ParseIP returns nil for them, so weed admin treats them as non-loopback). Also recognize the expanded IPv6 loopback form "0:0:0:0:0:0:0:1" in addition to "::1", matching net.ParseIP behavior for the two common representations. |
||
|
|
d997fba157 | 4.46 | ||
|
|
27b2411cdd |
fix(chart): add missing [grpc.s3] TLS section — S3 internal gRPC served plaintext while peers dial mTLS (#11157)
* fix(chart): serve S3 internal gRPC with mTLS when security enabled The security.toml generated by the chart has no [grpc.s3] section, so security.LoadServerTLS(viper, "grpc.s3") returns nil in weed/command/s3.go and the S3 server listens plaintext on its gRPC port (httpPort+10000 = 18333 by default). Workers dial that port with mTLS credentials (grpc.worker), producing: walker dispatch ...: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: tls: first record does not look like a TLS handshake" This breaks the s3_lifecycle worker's LifecycleDelete RPC path (recovery walk, daily replay) and any S3->S3 IAM cache propagation would fail the same way if clients enforced TLS. Add [grpc.s3] reusing the client cert already mounted on s3 pods (or s3.tlsSecret when set, mirroring the seaweedfs.s3.tlsArgs helper for the HTTPS listener). Co-Authored-By: Athena 🏛️ <hermes-agent@local> (custom / Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf) * fix(chart): always use internal client cert for grpc.s3 identity s3.tlsSecret is the public HTTPS listener certificate (possibly issued by a public CA); internal gRPC peers only trust grpc.ca, so presenting it on the internal gRPC port would break lifecycle/IAM RPC verification. Keep the two trust domains separate. Co-Authored-By: Athena 🏛️ <hermes-agent@local> (custom / Qwen3.8-Flash-Next-ROCmFP4) |
||
|
|
59916d8978 |
helm: values-driven labels on every ingress (#11127)
* helm: values-driven labels on every ingress Each ingress already takes annotations from values, but its labels were a fixed block, so tools that select ingresses by label (ExternalDNS label filters, for one) had nothing to key on. Every ingress block now has a labels map rendered after the standard app.kubernetes.io labels, including the Traefik IngressRouteTCP that shares the filer gRPC values. Claude-Session: https://claude.ai/code/session_01L6eJGXtYkwe1W9QjGeUgr1 * helm ci: render check for ingress labels Claude-Session: https://claude.ai/code/session_01L6eJGXtYkwe1W9QjGeUgr1 |
||
|
|
1d335357d6 |
helm: name both replication keys in the enableReplication comment (#11103)
The comment said enableReplication overrides "master & filer defaultReplicaPlacement", but the two components take different keys: master.defaultReplication and filer.defaultReplicaPlacement. Claude-Session: https://claude.ai/code/session_014Yr1Asxq3qTjJo2r9G4cLX |
||
|
|
79b8720213 | 4.45 | ||
|
|
40f77503d0 |
helm: trim the Lance chart comments (#10940)
Comments only, no rendering change: the values paragraphs compress to the density of the file around them, the env-var note becomes a template comment instead of leaking into the rendered manifest, and the two spots that invite a wrong simplification - the unconditionally rendered -port.lance and the empty-placeholder platform guard - each get their one-line why. Claude-Session: https://claude.ai/code/session_01Rkp1Mw5E89Jp6dzJFYiMrm |
||
|
|
d9d7d0be74 |
helm: serve the Lance catalog and deploy the Rust worker (#10936)
* helm: serve the S3 gateway's Lance Namespace, on by default Standalone `weed s3` serves the Lance Namespace API on 9101 unless told not to, so the chart defaulting s3.lancePort to 9101 matches weed's own posture instead of hiding the port behind a null. The flag is always rendered, so lancePort: 0 reaches weed as -port.lance=0 and genuinely disables the namespace rather than silently falling back to the binary default; 0 also drops the service port and the optional lanceIngress, which otherwise mirror the iceberg wiring. The NetworkPolicy admits the port the same way it admits icebergPort. Claude-Session: https://claude.ai/code/session_01Rkp1Mw5E89Jp6dzJFYiMrm * helm: run the Lance maintenance worker beside the Go worker The Go and Rust workers have no overlapping jobs - Go serves vacuum, balance, EC and iceberg_maintenance, only /usr/bin/weed-worker serves the lance_* family - so a cluster serving Lance tables needs both, not an either/or switch. The worker deployment now adds a worker-lance container whenever the namespace is reachable: worker.namespaceUrl, or derived from the release's S3 service and s3.lancePort. Untouched Go container; admin address derived the same way; mTLS flags point at the already-mounted worker cert when security is on; metrics on their own worker.lanceMetricsPort (9328, next in the 932x convention) with the same health probes, service port and scrape endpoint the Go container gets, and the worker NetworkPolicy admits that port exactly when the container renders. The image carries an empty placeholder on armv7/386 where exec falls back to the shell and exits 0, so the command refuses those platforms by name; s3.lancePort: 0 is the escape hatch there. Claude-Session: https://claude.ai/code/session_01Rkp1Mw5E89Jp6dzJFYiMrm |
||
|
|
115756dd41 | helm: expose loadBalancerClass, loadBalancerIP, loadBalancerSourceRanges on services (#10929) | ||
|
|
3563738699 | 4.44 | ||
|
|
4af6798639 |
helm: render the mysql secret and env only for the mysql filer store (#10872)
The db credential secret and the filer's WEED_MYSQL_* env were gated on filer.enabled alone, so a filer on mongodb, redis, postgres or leveldb2 got a generated mysql secret it never reads - kept forever by resource-policy: keep - plus a mysql-db-host pointing nowhere. Gate all of it on WEED_MYSQL_ENABLED, which is how the store is selected, plain keys and secret-backed ones alike. An enable flag the chart cannot read - a valueFrom, or one in secretExtraEnvironmentVars - counts as selected, so nothing is dropped from a filer that is actually on mysql. |
||
|
|
96304b6870 |
S3: source config credentials from the environment, and let the chart point at an existing secret (#10868)
* s3: resolve ${VAR} in static config credentials from the environment
A deployment that keeps its S3 keys in a secret store had no way to hand
them to the gateway: -config takes a file, so the keys had to be written
into that file. Let a key in the static config name an environment
variable instead, and drop any credential whose reference stays unset so
the placeholder never becomes a usable key.
* helm: source the generated s3 identities from an existing secret
The only way to reuse credentials that already live in a Secret was to
hand-author the whole seaweedfs_s3_config JSON, since the literal keys in
values.yaml end up in git and a lookup-based keyRef renders empty under
helm template and Argo CD. Let s3.credentials.admin/read name a Secret and
its keys instead: the generated config references them as ${VAR} and the
gateway resolves them from the environment, so nothing is read from the
cluster at render time.
* s3: treat an empty environment value as an unresolved credential reference
A secret store can hand over a key that exists but is blank. Resolving it
would leave an access key whose signing secret is empty, so count it as
unresolved and drop the credential.
* helm: render the s3 secret when only the all-in-one auth flag is set
The all-in-one deployment mounts the s3 secret whenever any of the three
enableAuth flags is set, but the secret itself only rendered for the s3 and
filer flags, so allInOne.s3.enableAuth on its own left the pod waiting on a
secret nothing creates.
* helm ci: check the credential wiring on every workload that mounts it
The render check only looked at the standalone s3 deployment and only at
one of the four variables, so a helper that bound a variable to the wrong
secret key would still pass.
* helm: create the all-in-one s3 secret for every flag that mounts it
The all-in-one pod mounts the secret on any of the three enableAuth flags,
so keying its creation off allInOne.s3.enableAuth alone still left
filer.s3.enableAuth without filer.s3.enabled pointing at a secret nothing
creates. Mirror the deployment's own condition instead, and check each
flag renders both the mount and the secret.
* s3: reject a malformed credential reference instead of keying on it
A typo such as ${MY-VAR} matches no substitution, so it survived expansion
and the placeholder itself became the access key the gateway accepted.
Require every ${ in a static credential to open a well-formed reference.
|
||
|
|
6c7f184381 | 4.43 | ||
|
|
9a8b204a7e |
Add monitoring label to filer servicemonitor (#10835)
* Add monitoring label to filer servicemonitor * helm: label the headless filer service, not the client one The ServiceMonitor takes its job label from the service name, and the bundled dashboard queries job="seaweedfs-filer". Selecting the client service would have renamed the job and blanked those panels. The headless service also publishes not-ready addresses, so it keeps reporting while a filer is starting up or shutting down. --------- Co-authored-by: Chris Lu <chris.lu@gmail.com> |
||
|
|
f04da8e9ad | 4.42 | ||
|
|
01e97955f9 | Helm: Add support for schedulerName (#10716) | ||
|
|
fb92d46e2d |
helm: enterprise license Secret, and a persistent-claim option for master data (#10601)
* helm: mount an enterprise license Secret into every component
Running the enterprise image under this chart meant hand-rolling
extraVolumes and extraVolumeMounts on every component. Missing one is
easy and quiet: a component without the license silently drops to
community mode, and on the admin that surfaces only as Data Recovery and
Point-in-Time Recovery refusing to enable, with the master looking fine.
Add global.seaweedfs.license.existingSecret. The Secret is mounted
read-only into master, volume, filer, s3, sftp, admin, worker and
all-in-one, and SEAWEED_LICENSE points every process at the file rather
than relying on the binary's search paths, which depend on the working
directory.
The mount is a directory, never a subPath: kubelet refreshes Secret
contents in place, but a subPath is resolved once at container start and
never updates, which would break license renewal. There is deliberately
no checksum annotation on the pod template either — that would roll every
pod on renewal, the opposite of what is wanted. Verified on kind: the
renewed file reached a running master ~70s after the Secret was patched,
same pod UID, restartCount 0.
Also documents that only the master re-reads the license on a timer
today; the other components pick a renewal up on their next restart.
* helm: keep master data on a claim by default
The master's -mdir holds its Raft log and snapshots, and with them the
cluster's topology UUID — the identity an enterprise license is issued
against. It defaulted to a hostPath under /ssd, which does not follow a
rescheduled pod: the master came back with an empty data directory, a
freshly generated cluster UUID, and a license that no longer matched.
With the chart's default of a single master replica there is no peer to
recover the identity from either.
Default master.data.type to persistentVolumeClaim, sized 1Gi (Raft state
is small). hostPath stays available for anyone who wants it.
This is breaking for existing releases: volumeClaimTemplates is immutable,
so helm upgrade on a release installed with the old default fails with
"updates to statefulset spec for fields other than ... are forbidden".
Verified on kind, along with both ways out — pinning
master.data.type=hostPath upgrades cleanly, and the documented migration
(stop the master, pre-seed a claim named after the StatefulSet, upgrade)
preserves the cluster UUID. Seeding has to happen while the master is
stopped; copying into a live pod loses the state, since the running
master rewrites its Raft files before the restart.
* helm: mount the license on masters only
The master is what reads the license file: it validates it, enforces the
capacity limit and binds it to the cluster UUID. Mounting the Secret on
volume, filer, s3, sftp, admin and worker put it in six more containers
that never look at it, so drop it there and keep master plus all-in-one,
which runs `weed server -master`.
Two fixes from review while here:
- project only the configured key out of the Secret, so an unrelated
key in the same Secret is not exposed to the container. Verified the
key-scoped projection still updates in place: patching the Secret
reached the running master in ~50s, same pod UID, restartCount 0.
- drop SEAWEED_LICENSE from merged extraEnvironmentVars while a
license Secret is configured. It used to be possible to render the
key twice in one container, with the user's value winning over the
path the chart actually mounts.
CI now pins the scope (master only, all-in-one separately), the
key-scoped projection, readOnly, and that SEAWEED_LICENSE renders once.
* helm: fix the documented master-data migration
The seed pod in the migration never mounted the claim it was supposed to
seed, so following the steps verbatim copied the Raft state onto the
pod's ephemeral filesystem and threw it away with the pod — landing the
reader in exactly the empty-claim, new-cluster-UUID state the section
exists to avoid. Give the pod the volume.
The names were assembled as <release>-seaweedfs-*, which is wrong
whenever the release name already contains the chart name or an override
is set; read the StatefulSet name from the cluster instead and derive the
claim from it. Also scope the procedure to the chart's single-master
default, and create the Secret in the release namespace.
Trims the enterprise prose this section had accumulated: this is the OSS
chart, and the master-data default is a durability fix that stands on its
own.
* helm: quote the projected license key, reserve it on the secret env path
A secretKey that YAML reads as a non-string (123, yes, no) rendered
unquoted into the volume's items, so the projection would not match the
Secret's key. Quote both key and path.
all-in-one renders secretExtraEnvironmentVars itself, outside the merge
helper that already drops SEAWEED_LICENSE, so an entry there could still
render the variable twice. Skip it there too while a license Secret is
configured. The master template has no such block, so this is the only
remaining path.
* helm: correct the license helper comments after scoping to masters
* helm: keep hostPath as the master data default
Defaulting master.data.type to a claim broke every existing release:
volumeClaimTemplates is immutable on a StatefulSet, so helm upgrade
failed with "updates to statefulset spec for fields other than ... are
forbidden" before it changed anything.
Keep hostPath as the default and document the claim as the option to
choose — for a new install, or for an existing one via the migration
already in the README. The chart supported both types all along; only
the default moves back.
Every immutable field of every rendered StatefulSet is now identical to
upstream under default values, so an in-place upgrade cannot trip the
API. Verified on kind: install with the unmodified upstream chart,
upgrade to this branch (ok), upgrade again turning the license Secret on
(ok, volume added in place). A fresh install with
master.data.type=persistentVolumeClaim binds its claim as before.
The whole PR is additive now: nothing renders differently until a value
is set.
* helm: scope the migration's StatefulSet lookup to the release
* helm: trim the comments added by this change
|