Compare commits

...

392 Commits

Author SHA1 Message Date
dependabot[bot] 50d41559ab chore(deps): bump rand from 0.9.1 to 0.9.2
Bumps [rand](https://github.com/rust-random/rand) from 0.9.1 to 0.9.2.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/rand_core-0.9.1...rand_core-0.9.2)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.9.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:12:43 -04:00
dependabot[bot] a8ea265933 chore(deps): bump serde_json from 1.0.140 to 1.0.142
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.140 to 1.0.142.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.140...v1.0.142)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.142
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:06:22 -04:00
dependabot[bot] dad082b6c7 chore(deps): bump mlugg/setup-zig from 2.0.4 to 2.0.5
Bumps [mlugg/setup-zig](https://github.com/mlugg/setup-zig) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/mlugg/setup-zig/releases)
- [Commits](475c97be87...8d6198c65f)

---
updated-dependencies:
- dependency-name: mlugg/setup-zig
  dependency-version: 2.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:06:01 -04:00
dependabot[bot] 6271e697ed chore(deps): bump actions/download-artifact from 4.3.0 to 5.0.0
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.3.0 to 5.0.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](d3f86a106a...634f93cb29)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:05:57 -04:00
dependabot[bot] b1dd4e650a chore(deps): bump taiki-e/install-action from 2.56.19 to 2.58.9
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.56.19 to 2.58.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](c99cc51b30...2c73a741d1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.58.9
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:05:46 -04:00
dependabot[bot] 5e7e3eddb2 chore(deps): bump clap from 4.5.41 to 4.5.43
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.41 to 4.5.43.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.41...clap_complete-v4.5.43)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.43
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:05:29 -04:00
dependabot[bot] 7f3652c9b4 chore(deps): bump hyper-util from 0.1.15 to 0.1.16
Bumps [hyper-util](https://github.com/hyperium/hyper-util) from 0.1.15 to 0.1.16.
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.15...v0.1.16)

---
updated-dependencies:
- dependency-name: hyper-util
  dependency-version: 0.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:05:21 -04:00
dependabot[bot] 7948300cf5 chore(deps): bump actions/checkout from 4.2.2 to 5.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 5.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](11bd71901b...08c6903cd8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:05:18 -04:00
dependabot[bot] 6eb78120b7 chore(deps): bump docker/login-action from 3.4.0 to 3.5.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](74a5d14239...184bdaa072)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:05:05 -04:00
dependabot[bot] 18464c2ac8 chore(deps): bump testcontainers from 0.24.0 to 0.25.0
Bumps [testcontainers](https://github.com/testcontainers/testcontainers-rs) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/testcontainers/testcontainers-rs/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-rs/compare/0.24.0...0.25.0)

---
updated-dependencies:
- dependency-name: testcontainers
  dependency-version: 0.25.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:04:53 -04:00
dependabot[bot] 34b054122b chore(deps): bump indexmap from 2.9.0 to 2.10.0
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.9.0 to 2.10.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.9.0...2.10.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:43:41 -06:00
dependabot[bot] 41f01ba0df chore(deps): bump hyper-util from 0.1.14 to 0.1.15
Bumps [hyper-util](https://github.com/hyperium/hyper-util) from 0.1.14 to 0.1.15.
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.14...v0.1.15)

---
updated-dependencies:
- dependency-name: hyper-util
  dependency-version: 0.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:36:41 -06:00
dependabot[bot] c6f6b44b51 chore(deps): bump Swatinem/rust-cache from 2.7.8 to 2.8.0
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.8 to 2.8.0.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](9d47c6ad4b...98c8021b55)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:36:14 -06:00
dependabot[bot] d3a82c8b2b chore(deps): bump docker/setup-buildx-action from 3.10.0 to 3.11.1
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.10.0 to 3.11.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](b5ca514318...e468171a9d)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:35:55 -06:00
dependabot[bot] 4c8c73e603 chore(deps): bump taiki-e/install-action from 2.53.0 to 2.56.19
---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.56.19
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:34:15 -06:00
dependabot[bot] 30f49b6cef chore(deps): bump utoipa from 5.3.1 to 5.4.0
Bumps [utoipa](https://github.com/juhaku/utoipa) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/juhaku/utoipa/releases)
- [Changelog](https://github.com/juhaku/utoipa/blob/master/utoipa-rapidoc/CHANGELOG.md)
- [Commits](https://github.com/juhaku/utoipa/compare/utoipa-5.3.1...utoipa-5.4.0)

---
updated-dependencies:
- dependency-name: utoipa
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:33:59 -06:00
dependabot[bot] 66502de4f0 chore(deps): bump tokio from 1.45.1 to 1.46.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.45.1 to 1.46.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.45.1...tokio-1.46.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.46.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:30:58 -06:00
dependabot[bot] e0ec996d4d chore(deps): bump github/codeql-action from 3.28.19 to 3.29.3
---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.29.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:30:30 -06:00
dependabot[bot] 8515084c01 chore(deps): bump mlugg/setup-zig from 2.0.1 to 2.0.4
Bumps [mlugg/setup-zig](https://github.com/mlugg/setup-zig) from 2.0.1 to 2.0.4.
- [Release notes](https://github.com/mlugg/setup-zig/releases)
- [Commits](7dccf5e6d0...475c97be87)

---
updated-dependencies:
- dependency-name: mlugg/setup-zig
  dependency-version: 2.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:30:12 -06:00
dependabot[bot] 8c037d3406 chore(deps): bump clap from 4.5.40 to 4.5.41
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.40 to 4.5.41.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.40...clap_complete-v4.5.41)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 19:28:33 -06:00
dependabot[bot] abf0702404 chore(deps): bump nkeys from 0.4.4 to 0.4.5
Bumps [nkeys](https://github.com/wasmcloud/nkeys) from 0.4.4 to 0.4.5.
- [Release notes](https://github.com/wasmcloud/nkeys/releases)
- [Commits](https://github.com/wasmcloud/nkeys/compare/v0.4.4...v0.4.5)

---
updated-dependencies:
- dependency-name: nkeys
  dependency-version: 0.4.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-08 17:53:54 -06:00
Brooks Townsend 67d8b25f27 chore(chart): bump to latest app version
Signed-off-by: Brooks Townsend <brooks@cosmonic.com>
2025-06-18 11:03:44 -04:00
dependabot[bot] b376c3ae2b chore(deps): bump softprops/action-gh-release from 2.2.2 to 2.3.2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.2.2 to 2.3.2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](da05d55257...72f2c25fcb)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.3.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 14:04:52 -06:00
dependabot[bot] eec6ca1c03 chore(deps): bump clap from 4.5.39 to 4.5.40
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.39 to 4.5.40.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.39...clap_complete-v4.5.40)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.40
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 13:59:42 -06:00
dependabot[bot] cf9ef590b3 chore(deps): bump taiki-e/install-action from 2.52.7 to 2.53.0
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.52.7 to 2.53.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](92f69c1952...cfe1303741)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 13:58:36 -06:00
dependabot[bot] 2009753535 chore(deps): bump taiki-e/install-action from 2.52.4 to 2.52.7
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.52.4 to 2.52.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](735e593394...92f69c1952)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.52.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-10 20:14:19 -06:00
dependabot[bot] 6ffc096379 chore(deps): bump hyper-util from 0.1.13 to 0.1.14
Bumps [hyper-util](https://github.com/hyperium/hyper-util) from 0.1.13 to 0.1.14.
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.13...v0.1.14)

---
updated-dependencies:
- dependency-name: hyper-util
  dependency-version: 0.1.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-10 20:13:29 -06:00
dependabot[bot] 62b573183b chore(deps): bump github/codeql-action from 3.28.18 to 3.28.19
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.18 to 3.28.19.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](ff0a06e83c...fca7ace96b)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-10 20:11:47 -06:00
dependabot[bot] 254765a5db chore(deps): bump ossf/scorecard-action from 2.4.1 to 2.4.2
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.1 to 2.4.2.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](f49aabe0b5...05b42c6244)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 14:21:01 -06:00
dependabot[bot] 9ad8b52ffe chore(deps): bump clap from 4.5.38 to 4.5.39
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.38 to 4.5.39.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.38...clap_complete-v4.5.39)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.39
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 14:20:46 -06:00
dependabot[bot] cc394fb963 chore(deps): bump hyper-util from 0.1.12 to 0.1.13
Bumps [hyper-util](https://github.com/hyperium/hyper-util) from 0.1.12 to 0.1.13.
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.12...v0.1.13)

---
updated-dependencies:
- dependency-name: hyper-util
  dependency-version: 0.1.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 14:20:37 -06:00
dependabot[bot] 4f0be1c2ec chore(deps): bump docker/build-push-action from 6.17.0 to 6.18.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.17.0 to 6.18.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](1dc7386353...263435318d)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 14:20:21 -06:00
dependabot[bot] c6177f1ec0 chore(deps): bump taiki-e/install-action from 2.52.1 to 2.52.4
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.52.1 to 2.52.4.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](6c6479b498...735e593394)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.52.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 14:19:38 -06:00
dependabot[bot] 9ab6ef3f3a chore(deps): bump hyper-util from 0.1.11 to 0.1.12
Bumps [hyper-util](https://github.com/hyperium/hyper-util) from 0.1.11 to 0.1.12.
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.11...v0.1.12)

---
updated-dependencies:
- dependency-name: hyper-util
  dependency-version: 0.1.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-28 16:10:04 -04:00
dependabot[bot] aab70fa276 chore(deps): bump mlugg/setup-zig from 2.0.0 to 2.0.1
Bumps [mlugg/setup-zig](https://github.com/mlugg/setup-zig) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/mlugg/setup-zig/releases)
- [Commits](aa9ad5c14e...7dccf5e6d0)

---
updated-dependencies:
- dependency-name: mlugg/setup-zig
  dependency-version: 2.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-28 16:08:09 -04:00
dependabot[bot] 04862520cb chore(deps): bump taiki-e/install-action from 2.51.2 to 2.52.1
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.51.2 to 2.52.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](941e8a4d9d...6c6479b498)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.52.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-28 16:07:52 -04:00
dependabot[bot] d24a275f69 chore(deps): bump uuid from 1.16.0 to 1.17.0
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.16.0 to 1.17.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.16.0...v1.17.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-28 16:07:37 -04:00
dependabot[bot] dc85b32bed chore(deps): bump tokio from 1.45.0 to 1.45.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.45.0 to 1.45.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.45.0...tokio-1.45.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.45.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-28 16:07:21 -04:00
dependabot[bot] a5a61d2749 chore(deps): bump tokio from 1.44.2 to 1.45.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.44.2 to 1.45.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.2...tokio-1.45.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-20 09:42:00 -04:00
dependabot[bot] c065b3e17e chore(deps): bump clap from 4.5.37 to 4.5.38
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.37 to 4.5.38.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.37...clap_complete-v4.5.38)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 21:39:12 -06:00
dependabot[bot] 4239d6d898
chore(deps): bump github/codeql-action from 3.28.17 to 3.28.18 (#669)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.17 to 3.28.18.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](60168efe1c...ff0a06e83c)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-19 08:56:32 -05:00
dependabot[bot] d240b53a5d
chore(deps): bump docker/build-push-action from 6.16.0 to 6.17.0 (#668)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.16.0 to 6.17.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](14487ce63c...1dc7386353)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-19 08:44:15 -05:00
dependabot[bot] 4e014223b8
chore(deps): bump taiki-e/install-action from 2.50.10 to 2.51.2 (#667)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.50.10 to 2.51.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](83254c5438...941e8a4d9d)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.51.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-19 08:31:15 -05:00
dependabot[bot] 96aa54bd5e chore(deps): bump mlugg/setup-zig from 1.2.1 to 2.0.0
Bumps [mlugg/setup-zig](https://github.com/mlugg/setup-zig) from 1.2.1 to 2.0.0.
- [Release notes](https://github.com/mlugg/setup-zig/releases)
- [Commits](a67e68dc5c...aa9ad5c14e)

---
updated-dependencies:
- dependency-name: mlugg/setup-zig
  dependency-version: 2.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-12 09:38:59 -04:00
dependabot[bot] 67b1d85ba9 chore(deps): bump taiki-e/install-action from 2.50.7 to 2.50.10
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.50.7 to 2.50.10.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](86c23eed46...83254c5438)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.50.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-12 09:36:04 -04:00
Joonas Bergius b5133163ae
chore: Switch to mlugg/setup-zig action (#662)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2025-05-06 08:29:49 -05:00
dependabot[bot] d5a77cc74c chore(deps): bump sha2 from 0.10.8 to 0.10.9
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.10.8 to 0.10.9.
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.8...sha2-v0.10.9)

---
updated-dependencies:
- dependency-name: sha2
  dependency-version: 0.10.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 22:31:25 -06:00
dependabot[bot] ef80b684ba chore(deps): bump chrono from 0.4.40 to 0.4.41
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.40 to 0.4.41.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.40...v0.4.41)

---
updated-dependencies:
- dependency-name: chrono
  dependency-version: 0.4.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 22:31:00 -06:00
dependabot[bot] ee40750113
chore(deps): bump testcontainers from 0.23.3 to 0.24.0 (#659)
Bumps [testcontainers](https://github.com/testcontainers/testcontainers-rs) from 0.23.3 to 0.24.0.
- [Release notes](https://github.com/testcontainers/testcontainers-rs/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-rs/compare/0.23.3...0.24.0)

---
updated-dependencies:
- dependency-name: testcontainers
  dependency-version: 0.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 19:04:41 -05:00
dependabot[bot] 73dc76b72a
chore(deps): bump taiki-e/install-action from 2.50.3 to 2.50.7 (#657)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.50.3 to 2.50.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ab3728c7ba...86c23eed46)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.50.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 08:59:17 -05:00
dependabot[bot] aac1e46d0b
chore(deps): bump github/codeql-action from 3.28.16 to 3.28.17 (#658)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.28.17.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](28deaeda66...60168efe1c)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 08:58:52 -05:00
dependabot[bot] e843cfb824 chore(deps): bump rand from 0.9.0 to 0.9.1
Bumps [rand](https://github.com/rust-random/rand) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.9.0...rand_core-0.9.1)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.9.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 14:28:33 -04:00
dependabot[bot] 0ef3162684 chore(deps): bump github/codeql-action from 3.28.15 to 3.28.16
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.15 to 3.28.16.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](45775bd823...28deaeda66)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 14:28:10 -04:00
dependabot[bot] 726a6c0bc7 chore(deps): bump actions/download-artifact from 4.2.1 to 4.3.0
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.2.1 to 4.3.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](95815c38cf...d3f86a106a)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 14:28:02 -04:00
dependabot[bot] f1a3acbf1e chore(deps): bump actions/setup-python from 5.5.0 to 5.6.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](8d9ed9ac5c...a26af69be9)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 14:27:48 -04:00
dependabot[bot] e92e526dfe chore(deps): bump docker/build-push-action from 6.15.0 to 6.16.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.15.0 to 6.16.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](471d1dc4e0...14487ce63c)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 14:27:33 -04:00
dependabot[bot] 15ae8c4d6a chore(deps): bump taiki-e/install-action from 2.49.50 to 2.50.3
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.49.50 to 2.50.3.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](09dc018eee...ab3728c7ba)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.50.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 14:27:18 -04:00
dependabot[bot] 22fc78860f
chore(deps): bump clap from 4.5.36 to 4.5.37 (#650)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.36 to 4.5.37.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.36...clap_complete-v4.5.37)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-21 10:18:24 -05:00
dependabot[bot] c7953f95e9
chore(deps): bump taiki-e/install-action from 2.49.49 to 2.49.50 (#649)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.49.49 to 2.49.50.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](be7c31b674...09dc018eee)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.49.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-21 10:17:46 -05:00
dependabot[bot] 7f0fc3a396
chore(deps): bump softprops/action-gh-release from 2.2.1 to 2.2.2 (#648)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](c95fe14893...da05d55257)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-21 10:16:15 -05:00
dependabot[bot] 37b47154e3 chore(deps): bump github/codeql-action from 3.28.13 to 3.28.15
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.13 to 3.28.15.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](1b549b9259...45775bd823)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:53:59 -04:00
dependabot[bot] 3c8b0742a5 chore(deps): bump actions/setup-python from 5.4.0 to 5.5.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](42375524e2...8d9ed9ac5c)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:51:20 -04:00
dependabot[bot] 8a3d21ce7d chore(deps): bump clap from 4.5.32 to 4.5.36
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.32 to 4.5.36.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.32...clap_complete-v4.5.36)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.36
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:50:57 -04:00
dependabot[bot] c09d40d335 chore(deps): bump tokio from 1.44.1 to 1.44.2
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.44.1 to 1.44.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.1...tokio-1.44.2)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.44.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:50:50 -04:00
dependabot[bot] 0748b04b60 chore(deps): bump indexmap from 2.8.0 to 2.9.0
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.8.0 to 2.9.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.8.0...2.9.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-version: 2.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:50:37 -04:00
dependabot[bot] dc1955370f chore(deps): bump hyper-util from 0.1.10 to 0.1.11
Bumps [hyper-util](https://github.com/hyperium/hyper-util) from 0.1.10 to 0.1.11.
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.10...v0.1.11)

---
updated-dependencies:
- dependency-name: hyper-util
  dependency-version: 0.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:50:28 -04:00
dependabot[bot] ebd113e51a chore(deps): bump anyhow from 1.0.97 to 1.0.98
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.97 to 1.0.98.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.97...1.0.98)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.98
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:50:21 -04:00
dependabot[bot] 8def8fe075 chore(deps): bump taiki-e/install-action from 2.49.34 to 2.49.49
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.49.34 to 2.49.49.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](914ac1e29d...be7c31b674)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.49.49
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:50:14 -04:00
dependabot[bot] 1ae4e8e2cb chore(deps): bump Swatinem/rust-cache from 2.7.7 to 2.7.8
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.7 to 2.7.8.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](f0deed1e0e...9d47c6ad4b)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 13:56:01 -06:00
dependabot[bot] db80173177 chore(deps): bump actions/download-artifact from 4.1.9 to 4.2.1
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.9 to 4.2.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](cc20338598...95815c38cf)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 13:54:17 -06:00
dependabot[bot] 6b4946dd32 chore(deps): bump taiki-e/install-action from 2.49.28 to 2.49.34
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.49.28 to 2.49.34.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](d7975a1de2...914ac1e29d)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 13:53:43 -06:00
dependabot[bot] 897192b894 chore(deps): bump github/codeql-action from 3.28.11 to 3.28.13
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.11 to 3.28.13.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](6bb031afdd...1b549b9259)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 13:50:33 -06:00
dependabot[bot] d715170d01 chore(deps): bump actions/upload-artifact from 4.6.1 to 4.6.2
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.1 to 4.6.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](4cec3d8aa0...ea165f8d65)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 13:48:15 -06:00
Taylor Thomas 8a1cd9e8e4 chore: Bump dep versions in preparation for release
Now that the control client is released, we can release wadm to continue
releasing the rest of the host monorepo

Signed-off-by: Taylor Thomas <taylor@cosmonic.com>
2025-03-19 15:00:57 -06:00
dependabot[bot] 93fbb9f4a3 chore(deps): bump taiki-e/install-action from 2.49.18 to 2.49.28
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.49.18 to 2.49.28.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](f87f9990b0...d7975a1de2)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 13:35:45 -04:00
dependabot[bot] 6e57d6f197 chore(deps): bump docker/login-action from 3.3.0 to 3.4.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](9780b0c442...74a5d14239)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 13:26:35 -04:00
dependabot[bot] b3ebcd2e2a chore(deps): bump ulid from 1.2.0 to 1.2.1
Bumps [ulid](https://github.com/dylanhart/ulid-rs) from 1.2.0 to 1.2.1.
- [Commits](https://github.com/dylanhart/ulid-rs/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: ulid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 13:26:19 -04:00
dependabot[bot] 6c8dd444ba chore(deps): bump indexmap from 2.7.1 to 2.8.0
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.7.1 to 2.8.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.7.1...2.8.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 13:26:06 -04:00
dependabot[bot] 005d599bcd chore(deps): bump tokio from 1.44.0 to 1.44.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.44.0 to 1.44.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.0...tokio-1.44.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 13:25:59 -04:00
dependabot[bot] 86af1498cb chore(deps): bump serde from 1.0.217 to 1.0.219
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.217 to 1.0.219.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.217...v1.0.219)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 13:25:50 -04:00
dependabot[bot] 60f0014449 chore(deps): bump async-trait from 0.1.87 to 0.1.88
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.87 to 0.1.88.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.87...0.1.88)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 13:25:42 -04:00
dependabot[bot] a329be44a3 chore(deps): bump testcontainers from 0.23.2 to 0.23.3
Bumps [testcontainers](https://github.com/testcontainers/testcontainers-rs) from 0.23.2 to 0.23.3.
- [Release notes](https://github.com/testcontainers/testcontainers-rs/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-rs/compare/0.23.2...0.23.3)

---
updated-dependencies:
- dependency-name: testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-12 14:13:08 -06:00
Brooks Townsend 14f7ed1bab chore(deps)!: upgrade async-nats to 0.39
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-03-12 16:05:02 -04:00
Brooks Townsend 39b79638ad chore(wadm): bump to 0.20.3 for release
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-03-11 14:41:37 -04:00
Brooks Townsend ac747cd8bc fix(scaler): react to config set events for components/providers
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-03-11 14:41:37 -04:00
dependabot[bot] 77f33f08f6 chore(deps): bump uuid from 1.13.1 to 1.15.1
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.13.1 to 1.15.1.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.13.1...v1.15.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 13:55:00 -06:00
dependabot[bot] 130c8f4a70 chore(deps): bump chrono from 0.4.39 to 0.4.40
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.39 to 0.4.40.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.39...v0.4.40)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 13:52:45 -06:00
dependabot[bot] e9f017b809 chore(deps): bump tokio from 1.43.0 to 1.44.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.43.0...tokio-1.44.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 13:49:53 -06:00
dependabot[bot] 1365854fbb chore(deps): bump thiserror from 2.0.11 to 2.0.12
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.11 to 2.0.12.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.11...2.0.12)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 13:43:35 -06:00
dependabot[bot] 8164b443fc chore(deps): bump azure/setup-helm from 4.2.0 to 4.3.0
Bumps [azure/setup-helm](https://github.com/azure/setup-helm) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/azure/setup-helm/releases)
- [Changelog](https://github.com/Azure/setup-helm/blob/main/CHANGELOG.md)
- [Commits](fe7b79cd5e...b9e51907a0)

---
updated-dependencies:
- dependency-name: azure/setup-helm
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 13:42:39 -06:00
dependabot[bot] 445622df2e chore(deps): bump ossf/scorecard-action from 2.4.0 to 2.4.1
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](62b2cac7ed...f49aabe0b5)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 13:42:09 -06:00
dependabot[bot] e218cdae70 chore(deps): bump github/codeql-action from 3.28.10 to 3.28.11
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.10 to 3.28.11.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b56ba49b26...6bb031afdd)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 13:41:37 -06:00
dependabot[bot] f74f7f8f54 chore(deps): bump taiki-e/install-action from 2.48.13 to 2.49.18
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.48.13 to 2.49.18.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ad0904967b...f87f9990b0)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 12:56:26 -06:00
dependabot[bot] 734c726f14 chore(deps): bump actions/download-artifact from 4.1.8 to 4.1.9
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.8 to 4.1.9.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](fa0a91b85d...cc20338598)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 12:54:15 -06:00
Nicolas Lamirault 0fba847245
feat(helm): Kubernetes labels (#598)
Signed-off-by: Nicolas Lamirault <nicolas.lamirault@gmail.com>
2025-03-06 19:28:57 -05:00
dependabot[bot] a2c022b462 chore(deps): bump anyhow from 1.0.95 to 1.0.97
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.95 to 1.0.97.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.95...1.0.97)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:42:00 -07:00
dependabot[bot] 4db8763a0f chore(deps): bump schemars from 0.8.21 to 0.8.22
Bumps [schemars](https://github.com/GREsau/schemars) from 0.8.21 to 0.8.22.
- [Release notes](https://github.com/GREsau/schemars/releases)
- [Changelog](https://github.com/GREsau/schemars/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GREsau/schemars/compare/v0.8.21...v0.8.22)

---
updated-dependencies:
- dependency-name: schemars
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:29:22 -07:00
dependabot[bot] 7958bfbced chore(deps): bump async-trait from 0.1.86 to 0.1.87
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.86 to 0.1.87.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.86...0.1.87)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:27:54 -07:00
dependabot[bot] 37eb784b82 chore(deps): bump serde_json from 1.0.138 to 1.0.140
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.138 to 1.0.140.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.138...v1.0.140)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:27:06 -07:00
dependabot[bot] 16191d081a chore(deps): bump actions/upload-artifact from 4.6.0 to 4.6.1
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](65c4c4a1dd...4cec3d8aa0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:26:39 -07:00
dependabot[bot] a5424b7e4c chore(deps): bump github/codeql-action from 3.28.9 to 3.28.10
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.9 to 3.28.10.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](9e8d0789d4...b56ba49b26)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:26:06 -07:00
dependabot[bot] 2e3abbcba0 chore(deps): bump docker/setup-qemu-action from 3.4.0 to 3.6.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.4.0 to 3.6.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](4574d27a47...29109295f8)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:25:04 -07:00
dependabot[bot] 720113d026 chore(deps): bump docker/setup-buildx-action from 3.9.0 to 3.10.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.9.0 to 3.10.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](f7ce87c1d6...b5ca514318)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:24:27 -07:00
dependabot[bot] 80bba4fb9f chore(deps): bump docker/build-push-action from 6.13.0 to 6.15.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.13.0 to 6.15.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](ca877d9245...471d1dc4e0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 10:23:56 -07:00
dependabot[bot] 2e474c5d0c
chore(deps): bump clap from 4.5.28 to 4.5.29 (#597)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.28 to 4.5.29.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.28...clap_complete-v4.5.29)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 09:36:09 -06:00
dependabot[bot] ceda608718
chore(deps): bump taiki-e/install-action from 2.48.9 to 2.48.13 (#596)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.48.9 to 2.48.13.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](995f97569c...ad0904967b)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 09:35:32 -06:00
dependabot[bot] 6b9d6fd26f chore(deps): bump uuid from 1.12.1 to 1.13.1
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.12.1 to 1.13.1.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.12.1...1.13.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 12:35:57 -05:00
dependabot[bot] 44753eb992 chore(deps): bump testcontainers from 0.23.1 to 0.23.2
Bumps [testcontainers](https://github.com/testcontainers/testcontainers-rs) from 0.23.1 to 0.23.2.
- [Release notes](https://github.com/testcontainers/testcontainers-rs/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-rs/compare/0.23.1...0.23.2)

---
updated-dependencies:
- dependency-name: testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 12:29:38 -05:00
dependabot[bot] c5694226c8 chore(deps): bump taiki-e/install-action from 2.48.1 to 2.48.9
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.48.1 to 2.48.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](510b3ecd79...995f97569c)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 12:29:22 -05:00
dependabot[bot] c808f7a07a chore(deps): bump ulid from 1.1.4 to 1.2.0
Bumps [ulid](https://github.com/dylanhart/ulid-rs) from 1.1.4 to 1.2.0.
- [Commits](https://github.com/dylanhart/ulid-rs/compare/v1.1.4...v1.2.0)

---
updated-dependencies:
- dependency-name: ulid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 12:29:17 -05:00
dependabot[bot] eaebdd918e chore(deps): bump clap from 4.5.27 to 4.5.28
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.27 to 4.5.28.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.27...clap_complete-v4.5.28)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 12:29:12 -05:00
dependabot[bot] e756aa038f chore(deps): bump docker/setup-buildx-action from 3.8.0 to 3.9.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.8.0 to 3.9.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](6524bf65af...f7ce87c1d6)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 12:28:58 -05:00
dependabot[bot] ba04447356 chore(deps): bump docker/setup-qemu-action from 3.3.0 to 3.4.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](53851d1459...4574d27a47)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 12:28:46 -05:00
dependabot[bot] 386eebd33f chore(deps): bump github/codeql-action from 3.28.8 to 3.28.9
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.8 to 3.28.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](dd746615b3...9e8d0789d4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 12:28:33 -05:00
Brooks Townsend 1926bf070f chore: ignore dependabot commits in release notes
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-02-11 11:48:34 -05:00
Brooks Townsend ddb912553a chore: patch all for release
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-02-11 11:48:34 -05:00
Stuart Harris bdf06dc5d9 only check configs that declare properties for unique names.
Signed-off-by: Stuart Harris <stuart.harris@red-badger.com>
2025-02-07 09:09:49 -05:00
dependabot[bot] ffc655e749 chore(deps): bump hyper from 1.5.2 to 1.6.0
Bumps [hyper](https://github.com/hyperium/hyper) from 1.5.2 to 1.6.0.
- [Release notes](https://github.com/hyperium/hyper/releases)
- [Changelog](https://github.com/hyperium/hyper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper/compare/v1.5.2...v1.6.0)

---
updated-dependencies:
- dependency-name: hyper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-05 10:47:05 -05:00
dependabot[bot] 7218266206 chore(deps): bump async-trait from 0.1.85 to 0.1.86
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.85 to 0.1.86.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.85...0.1.86)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 18:47:51 -05:00
dependabot[bot] cb00233aaa chore(deps): bump serde_json from 1.0.137 to 1.0.138
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.137 to 1.0.138.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.137...v1.0.138)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 18:44:45 -05:00
dependabot[bot] 7a94b8565c chore(deps): bump bytes from 1.9.0 to 1.10.0
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 18:44:19 -05:00
dependabot[bot] 66ca4cc9f5 chore(deps): bump softprops/action-gh-release from 2.0.9 to 2.2.1
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.9 to 2.2.1.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](e7a8f85e1c...c95fe14893)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 13:55:15 -05:00
luk3ark c8e715a088 fix(ci): correct wadm WIT tarball structure
Signed-off-by: luk3ark <luk3ark@gmail.com>
2025-02-03 13:47:34 -05:00
dependabot[bot] a5066c16dd
chore(deps): bump taiki-e/install-action from 2.47.25 to 2.48.1 (#579)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.47.25 to 2.48.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.47.25...510b3ecd7915856b6909305605afa7a8a57c1b04)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 10:59:16 -06:00
dependabot[bot] e4de5fc83e
chore(deps): bump actions/setup-python from 5.3.0 to 5.4.0 (#578)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](0b93645e9f...42375524e2)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 10:58:42 -06:00
dependabot[bot] b26427c3ec
chore(deps): bump github/codeql-action from 3.27.9 to 3.28.8 (#577)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.9 to 3.28.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](df409f7d92...dd746615b3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 10:13:28 -06:00
Taylor Thomas 2113aa3781 chore: Bumps versions for patch release
I also did a little housekeeping here to fix a bunch of clippy lints and
updated the flake inputs

Signed-off-by: Taylor Thomas <taylor@cosmonic.com>
2025-01-30 12:59:42 -05:00
dependabot[bot] 55444f27f2 chore(deps): bump uuid from 1.12.0 to 1.12.1
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.12.0...1.12.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 10:35:51 -05:00
dependabot[bot] 797eddf5c1 chore(deps): bump docker/build-push-action from 6.10.0 to 6.13.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.10.0 to 6.13.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](48aba3b46d...ca877d9245)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 10:28:22 -05:00
dependabot[bot] 55be7d8558 chore(deps): bump taiki-e/install-action from 2.46.11 to 2.47.25
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.46.11 to 2.47.25.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ed8c79bccf...1936c8cfe3)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 10:28:06 -05:00
dependabot[bot] 7d59eb4746 chore(deps): bump serde_json from 1.0.135 to 1.0.137
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.135 to 1.0.137.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.135...v1.0.137)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 10:27:47 -05:00
dependabot[bot] 4bb74d04fe chore(deps): bump wasmcloud-control-interface from 2.2.0 to 2.3.0
Bumps [wasmcloud-control-interface](https://github.com/wasmCloud/wasmCloud) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/wasmCloud/wasmCloud/releases)
- [Changelog](https://github.com/wasmCloud/wasmCloud/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasmCloud/wasmCloud/commits)

---
updated-dependencies:
- dependency-name: wasmcloud-control-interface
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 10:27:31 -05:00
dependabot[bot] 1f902b248c chore(deps): bump helm/chart-testing-action from 2.6.1 to 2.7.0
Bumps [helm/chart-testing-action](https://github.com/helm/chart-testing-action) from 2.6.1 to 2.7.0.
- [Release notes](https://github.com/helm/chart-testing-action/releases)
- [Commits](e6669bcd63...0d28d3144d)

---
updated-dependencies:
- dependency-name: helm/chart-testing-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 10:27:23 -05:00
dependabot[bot] 34fb5e69b2 chore(deps): bump clap from 4.5.26 to 4.5.27
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.26 to 4.5.27.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.26...clap_complete-v4.5.27)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 10:27:20 -05:00
luk3ark efeb6a020d fix(wadm): correct status topic name in WADM
Signed-off-by: luk3ark <luk3ark@gmail.com>
2025-01-27 10:25:29 -05:00
Taylor Thomas e492823998 fix(ci): Right tarball location
Signed-off-by: Taylor Thomas <taylor@cosmonic.com>
2025-01-24 14:15:20 -07:00
Taylor Thomas ad2cb51238 fix(ci): Passes the right directory for wit builds
Signed-off-by: Taylor Thomas <taylor@cosmonic.com>
2025-01-24 12:46:09 -07:00
luk3ark 95633628af feat(ci): add OCI publishing to WADM WIT workflow
Signed-off-by: luk3ark <luk3ark@gmail.com>
2025-01-23 16:53:51 -07:00
dependabot[bot] 9fbc598eff
chore(deps): bump actions/checkout from 4.1.1 to 4.2.2 (#557)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.2.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.1...11bd71901bbe5b1630ceea73d27597364c9af683)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 18:26:39 +00:00
dependabot[bot] 830b02545a
chore(deps): bump indexmap from 2.7.0 to 2.7.1 (#554)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.7.0 to 2.7.1.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.7.0...2.7.1)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:26:05 -06:00
dependabot[bot] 9475e4c542
chore(deps): bump semver from 1.0.24 to 1.0.25 (#553)
Bumps [semver](https://github.com/dtolnay/semver) from 1.0.24 to 1.0.25.
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.24...1.0.25)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:25:47 -06:00
dependabot[bot] 84d4f48783
chore(deps): bump thiserror from 2.0.9 to 2.0.11 (#552)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.9 to 2.0.11.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.9...2.0.11)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:25:25 -06:00
dependabot[bot] 95d256215b
chore(deps): bump uuid from 1.11.0 to 1.12.0 (#551)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.11.0...1.12.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:24:55 -06:00
dependabot[bot] 7e97f6e615
chore(deps): bump tokio from 1.42.0 to 1.43.0 (#550)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.42.0 to 1.43.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.42.0...tokio-1.43.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:23:55 -06:00
dependabot[bot] bcc2b7f461
chore(deps): bump Swatinem/rust-cache from 2.7.5 to 2.7.7 (#555)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.5 to 2.7.7.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](82a92a6e8f...f0deed1e0e)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:22:05 -06:00
dependabot[bot] 2aa35a9514
chore(deps): bump helm/kind-action from 1.11.0 to 1.12.0 (#556)
Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](ae94020eaf...a1b0e39133)

---
updated-dependencies:
- dependency-name: helm/kind-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:21:14 -06:00
dependabot[bot] f504e8c1b2
chore(deps): bump docker/setup-qemu-action from 3.2.0 to 3.3.0 (#558)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](49b3bc8e6b...53851d1459)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:20:14 -06:00
dependabot[bot] 7658a4e654
chore(deps): bump actions/upload-artifact from 4.4.3 to 4.6.0 (#559)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.3 to 4.6.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.4.3...65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 12:19:09 -06:00
Brooks Townsend 64e3d93118 refactor(wadm): better visibility controls
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Brooks Townsend 41e6e352cc chore: bump wadm to 0.20.0
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Brooks Townsend d169b1be62 test(wadm): fix relative path
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Brooks Townsend 4676947211 ci(wadm): test wadm crate feature combinations
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Brooks Townsend 78e077604e fix(wadm): properly gate imports behind http_admin
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Brooks Townsend a7a287ce7b chore(wadm): add http_admin feature
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Brooks Townsend 90dac77412 refactor: use cfg_attr to gate CLI config
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Brooks Townsend ab9ad612ee chore(deps): simplify CLI deps
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Brooks Townsend 18a66b2640 refactor(*)!: move start functionality to wadm lib
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-17 14:11:59 -05:00
Roman Volosatovs 13faa57248 feat: add HTTP admin endpoint
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
2025-01-14 09:32:34 -05:00
dependabot[bot] b167486f48 chore(deps): bump utoipa from 5.3.0 to 5.3.1
Bumps [utoipa](https://github.com/juhaku/utoipa) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/juhaku/utoipa/releases)
- [Changelog](https://github.com/juhaku/utoipa/blob/master/utoipa-rapidoc/CHANGELOG.md)
- [Commits](https://github.com/juhaku/utoipa/compare/utoipa-5.3.0...utoipa-5.3.1)

---
updated-dependencies:
- dependency-name: utoipa
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 10:01:27 -05:00
dependabot[bot] 52500b4787 chore(deps): bump serde_json from 1.0.134 to 1.0.135
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.134 to 1.0.135.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.134...v1.0.135)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 10:01:09 -05:00
dependabot[bot] 8df7924598 chore(deps): bump async-trait from 0.1.83 to 0.1.85
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.83 to 0.1.85.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.83...0.1.85)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 10:00:53 -05:00
dependabot[bot] 59e7e66562 chore(deps): bump clap from 4.5.23 to 4.5.26
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.23 to 4.5.26.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.23...clap_complete-v4.5.26)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 10:00:49 -05:00
dependabot[bot] f88140893b chore(deps): bump ulid from 1.1.3 to 1.1.4
Bumps [ulid](https://github.com/dylanhart/ulid-rs) from 1.1.3 to 1.1.4.
- [Commits](https://github.com/dylanhart/ulid-rs/compare/v1.1.3...v1.1.4)

---
updated-dependencies:
- dependency-name: ulid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 10:00:44 -05:00
Brooks Townsend 77f5bc8961 test(validation): allow misnamed interface as warning
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-03 16:30:50 -05:00
Brooks Townsend e67c9e580c fix(types): warn on unknown interface
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-03 16:30:50 -05:00
Brooks Townsend 4243efdc8f release(*): bump versions
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2025-01-02 10:42:13 -05:00
Márk Kővári 40d8b50c0e test(e2e): add one e2e test with memory persistence
Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2025-01-02 09:54:09 -05:00
Márk Kővári 5a4c13fe75 chore(nats): remove redundant possible values, clap already generates
those

Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2025-01-02 09:54:09 -05:00
Márk Kővári b6b398ecd7 chore(stream-persistence): remove hidden and lowercase options
Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2025-01-02 09:54:09 -05:00
Márk Kővári 6fc79d3c81 fix(typo): persistance to persistence
Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2025-01-02 09:54:09 -05:00
Márk Kővári 7a811a6737 fix(clap): enum pascalcase rename
Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2025-01-02 09:54:09 -05:00
Márk Kővári 1448671649 debug(storage): add storage default value failes WIP
Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2025-01-02 09:54:09 -05:00
Márk Kővári f596dadcb8 fix(clippy): resolve clippy warning with ToString
Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2025-01-02 09:54:09 -05:00
Márk Kővári ca868c5f79 feat(wadm): cli enable memory stream usage with flags
Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2025-01-02 09:54:09 -05:00
luk3ark 11aa88b73f feat(deps): removed default std feature and updated to target_family
Signed-off-by: luk3ark <luk3ark@gmail.com>
2025-01-02 09:53:05 -05:00
luk3ark 6b768c1607 feat(deps): change feature gate back to wit
Signed-off-by: luk3ark <luk3ark@gmail.com>
2025-01-02 09:53:05 -05:00
luk3ark c26eb6d2fd feat(deps): removed redundant dependencies
Signed-off-by: luk3ark <luk3ark@gmail.com>
2025-01-02 09:53:05 -05:00
luk3ark f34b19a79b feat(deps): add separate wit-wasm and wit-std features
Signed-off-by: luk3ark <luk3ark@gmail.com>
2025-01-02 09:53:05 -05:00
luk3ark 532e4930ef feat(deps): add separate wit-wasm and wit-std features
Signed-off-by: luk3ark <luk3ark@gmail.com>
2025-01-02 09:53:05 -05:00
luk3ark 6004c9a136 allow unique interfaces across duplicate links and test
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-31 11:12:40 -05:00
dependabot[bot] 4af2a727c3 chore(deps): bump serde from 1.0.216 to 1.0.217
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.216 to 1.0.217.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.216...v1.0.217)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 10:52:20 -07:00
dependabot[bot] d92b0b7e6a chore(deps): bump utoipa from 5.2.0 to 5.3.0
Bumps [utoipa](https://github.com/juhaku/utoipa) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/juhaku/utoipa/releases)
- [Changelog](https://github.com/juhaku/utoipa/blob/master/utoipa-rapidoc/CHANGELOG.md)
- [Commits](https://github.com/juhaku/utoipa/compare/utoipa-5.2.0...utoipa-5.3.0)

---
updated-dependencies:
- dependency-name: utoipa
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 10:47:12 -05:00
dependabot[bot] ab26db73b7 chore(deps): bump serde_json from 1.0.133 to 1.0.134
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.133 to 1.0.134.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.133...v1.0.134)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 10:47:00 -05:00
dependabot[bot] 229411893a chore(deps): bump anyhow from 1.0.94 to 1.0.95
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.94 to 1.0.95.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.94...1.0.95)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 10:46:45 -05:00
dependabot[bot] e2de3fe6b8 chore(deps): bump thiserror from 2.0.7 to 2.0.9
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.7 to 2.0.9.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.7...2.0.9)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 10:46:41 -05:00
Vikrant Palle 062130e6f1 improve test_healthy_providers_return_healthy_status unit test
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-23 09:10:29 -05:00
Vikrant Palle df0bf72cde nit: formatting fixes
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-23 09:10:29 -05:00
Vikrant Palle dad1bd9f66 use StatusType::Failed in scaler status
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-23 09:10:29 -05:00
Vikrant Palle a0da5ef75e add unhealthy status to bindings
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-23 09:10:29 -05:00
Vikrant Palle f1d68a87d5 refactor health check event handling
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-23 09:10:29 -05:00
Vikrant Palle b67193a9f8 add unhealthy status type
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-23 09:10:29 -05:00
Vikrant Palle 764e90ba1b add unit tests
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-23 09:10:29 -05:00
Vikrant Palle 50b672ad30 reflect unhealthy providers in spreadscaler + daemonscaler
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-23 09:10:29 -05:00
Márk Kővári 265f732fc8 feat(validation): warn for link props source_configs and target_configs
Signed-off-by: Márk Kővári <kovarimarkofficial@gmail.com>
2024-12-20 09:32:57 -07:00
Florian Fürstenberg b2a1082559 fix(server): Removed not needed arguments for checking for duplicate link config names (#478)
Signed-off-by: Florian Fürstenberg <florian.fuerstenberg@posteo.de>
2024-12-19 09:21:22 -05:00
Florian Fürstenberg 341ae617ec fix(server): Added validation logic for duplicated link config names (#478)
Signed-off-by: Florian Fürstenberg <florian.fuerstenberg@posteo.de>
2024-12-19 09:21:22 -05:00
Florian Fürstenberg a6223a3f74 fix(server): Added validation for duplicated link config names (#478)
Signed-off-by: Florian Fürstenberg <florian.fuerstenberg@posteo.de>
2024-12-19 09:21:22 -05:00
Taylor Thomas 38cb50f364 chore: Polishes up flake with a few more clarifying comments
Signed-off-by: Taylor Thomas <taylor@cosmonic.com>
2024-12-17 12:30:03 -07:00
Roman Volosatovs 2b50ef2877 feat: filter `Cargo.toml`
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
2024-12-17 12:30:03 -07:00
Taylor Thomas 97e9e32066 feat(flake): Attempts to break up deps some more in the flake
Signed-off-by: Taylor Thomas <taylor@cosmonic.com>
2024-12-17 12:30:03 -07:00
Taylor Thomas c2ae9f2643 feat: Adds flake
Adds a nix flake for usage in building things. It is still missing the
ability to run an e2e test and build docker images, but it does work
for both building and nix shell

Signed-off-by: Taylor Thomas <taylor@oftaylor.com>
2024-12-17 12:30:03 -07:00
Joonas Bergius 864acfd28e
chore(ci): Pin GitHub Actions dependencies (#523)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-12-17 12:04:35 -06:00
dependabot[bot] 994b881701 chore(deps): bump thiserror from 1.0.69 to 2.0.6
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.69 to 2.0.6.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.69...2.0.6)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 13:02:57 -05:00
dependabot[bot] 2cc4092daa
chore(deps): bump github/codeql-action from 3.27.6 to 3.27.9 (#520)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.6 to 3.27.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](aa57810251...df409f7d92)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-16 16:30:52 -06:00
dependabot[bot] e1d665416e
chore(deps): bump helm/kind-action from 1.10.0 to 1.11.0 (#519)
Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](https://github.com/helm/kind-action/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: helm/kind-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-16 11:16:29 -06:00
dependabot[bot] 6e8eb504c9
chore(deps): bump ossf/scorecard-action from 2.3.1 to 2.4.0 (#521)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.1 to 2.4.0.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](0864cf1902...62b2cac7ed)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-16 11:15:47 -06:00
luk3ark 7d80eca6aa remove unneeded wasm feature and revert naming
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-13 12:17:41 -07:00
luk3ark 54bf5cbb61 added feature flag for unused import
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-13 12:17:41 -07:00
luk3ark 65cfd337f6 fix
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-13 12:17:41 -07:00
luk3ark 87c64bdcd9 fix test dependency
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-13 12:17:41 -07:00
luk3ark 505debf7ff fix typo
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-13 12:17:41 -07:00
luk3ark c898e2eb20 added wasm flag for wadm-types
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-13 12:17:41 -07:00
luk3ark 5919660776 added wasm flag for wadm-types
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-13 12:17:41 -07:00
Florian Fürstenberg c1db5ff946 fix(server): Added missing test for test_delete_noop (#502)
Signed-off-by: Florian Fürstenberg <florian.fuerstenberg@posteo.de>
2024-12-12 10:34:23 -07:00
Florian Fürstenberg 163c28269a fix(server): Cover DeleteResult::Noop for delete_model if no version was specified (#502)
Signed-off-by: Florian Fürstenberg <florian.fuerstenberg@posteo.de>
2024-12-12 10:34:23 -07:00
Vikrant Palle e9c7cf4ab1 nit: move hashset inside loop
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-12 12:34:07 -05:00
Vikrant Palle f137a9ab60 change duplicate link definition
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-12 12:34:07 -05:00
Vikrant Palle d9c3627547 add check for duplicate links
Signed-off-by: Vikrant Palle <vikrantpalle@gmail.com>
2024-12-12 12:34:07 -05:00
luk3ark e8fe31f0ed added explicit generates
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-12 09:59:48 -07:00
luk3ark 18e5566a5e chore: bump wadm-types to 0.9.0 for wit-bindgen-wrpc
Signed-off-by: luk3ark <luk3ark@gmail.com>
2024-12-12 09:59:48 -07:00
Joonas Bergius 2561838039
chore: Add Security Policy with link to the main repository (#508)
Signed-off-by: Joonas Bergius <joonas@bergi.us>
2024-12-10 10:14:34 -06:00
dependabot[bot] 8c0ea8263d chore(deps): bump chrono from 0.4.38 to 0.4.39
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.38 to 0.4.39.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.38...v0.4.39)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-10 09:35:35 -05:00
Joonas Bergius ae8ab69f24
chore: Fix scorecard workflow spacing (#506)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-12-09 15:05:19 -05:00
dependabot[bot] 61b81112bd
chore(deps): bump tokio from 1.41.1 to 1.42.0 (#510)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.41.1 to 1.42.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.41.1...tokio-1.42.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 12:29:26 -06:00
dependabot[bot] b2207ef41f
chore(deps): bump clap from 4.5.21 to 4.5.23 (#511)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.21 to 4.5.23.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.21...clap_complete-v4.5.23)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 12:28:36 -06:00
dependabot[bot] 0cc63485f4
chore(deps): bump anyhow from 1.0.93 to 1.0.94 (#512)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.93 to 1.0.94.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.93...1.0.94)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 10:38:40 -06:00
Joonas Bergius 31cf33a9b7
chore: Add OSSF Scorecard workflow (#504)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-12-06 15:28:16 -05:00
Joonas Bergius fb2b74532b
fix: RUSTSEC-2024-0402 (#503)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-12-06 15:28:01 -05:00
Ahmed Tadde ca5a63104a
fix: detect spread scaler requirements violation (#491)
---------

Signed-off-by: Ahmed <ahmedtadde@gmail.com>
Co-authored-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-12-05 01:21:58 -05:00
Joonas Bergius 21feab093f
chore(ci): Set token permissions for GitHub Actions workflows (#498)
Signed-off-by: Joonas Bergius <joonas@bergi.us>
2024-12-02 17:31:16 +00:00
dependabot[bot] eb6fce9255 chore(deps): bump thiserror from 1.0.65 to 1.0.69
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.65 to 1.0.69.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.65...1.0.69)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 10:38:08 -05:00
dependabot[bot] 087203cdbc chore(deps): bump indexmap from 2.6.0 to 2.7.0
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.6.0 to 2.7.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.6.0...2.7.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 10:38:01 -05:00
dependabot[bot] 6e35596a22 chore(deps): bump bytes from 1.8.0 to 1.9.0
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 10:37:45 -05:00
dependabot[bot] 2d47f32fc5
chore(deps): bump serde_json from 1.0.132 to 1.0.133 (#496)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.132 to 1.0.133.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.132...v1.0.133)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-30 09:39:59 -06:00
Joonas Bergius 2c00cada86
chore(wadm-cli): prune unused dependencies (#487)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-20 00:29:15 -06:00
Joonas Bergius d1b9d925d2
chore(wadm): prune unused dependencies (#486)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-20 00:28:58 -06:00
Joonas Bergius db38c50600
chore(wadm-client): prune unused dependencies (#485)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-20 00:28:44 -06:00
Joonas Bergius 964a586ab6
chore(wadm-types): prune unused dependencies (#484)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-20 00:28:31 -06:00
Sudhanshu Pandey 6c425a198c
chore: Update the Github Action to set correct tag for the Docker Image (#493)
Signed-off-by: Sudhanshu Pandey <sp6370@nyu.edu>
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
Co-authored-by: Joonas Bergius <joonas@users.noreply.github.com>
2024-11-20 00:27:37 -06:00
dependabot[bot] 0fb04cfee4
chore(deps): bump serde from 1.0.214 to 1.0.215 (#495)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.214 to 1.0.215.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.214...v1.0.215)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 15:44:10 +00:00
dependabot[bot] 066eccdbd2
chore(deps): bump clap from 4.5.20 to 4.5.21 (#494)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.20 to 4.5.21.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.20...clap_complete-v4.5.21)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 15:43:54 +00:00
dependabot[bot] 4bd2560bdd
chore(deps): bump anyhow from 1.0.92 to 1.0.93 (#490)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.92 to 1.0.93.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.92...1.0.93)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 09:40:58 -06:00
dependabot[bot] 57e1807be8
chore(deps): bump tokio from 1.41.0 to 1.41.1 (#489)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.41.0 to 1.41.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.41.0...tokio-1.41.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 09:40:50 -06:00
dependabot[bot] ef32c26fa0
chore(deps): bump serial_test from 3.1.1 to 3.2.0 (#488)
Bumps [serial_test](https://github.com/palfrey/serial_test) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/palfrey/serial_test/releases)
- [Commits](https://github.com/palfrey/serial_test/compare/v3.1.1...v3.2.0)

---
updated-dependencies:
- dependency-name: serial_test
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 09:40:18 -06:00
Victor Adossi 1c4b706b17
chore(dx): remove deprecated crates extension (#492)
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
2024-11-14 10:32:43 -07:00
Joonas Bergius c48802566e
chore: Bump client and types 0.7.1 (#483)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-09 09:34:16 -06:00
Joonas Bergius 42cc8672d1
chore(ci): pin zig to latest stable version (#482)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-09 09:29:39 -05:00
Joonas Bergius 9272799f62
chore: Bump wasmcloud-secrets-types (#481)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-09 09:29:30 -05:00
Joonas Bergius cebb511d28
chore: Bump wascap to 0.15.2 (#480)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-08 19:58:07 -06:00
dependabot[bot] d0faba952d chore(deps): bump serde from 1.0.213 to 1.0.214
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.213 to 1.0.214.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.213...v1.0.214)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 09:44:49 -05:00
dependabot[bot] f59cfa2f7d chore(deps): bump utoipa from 5.1.3 to 5.2.0
Bumps [utoipa](https://github.com/juhaku/utoipa) from 5.1.3 to 5.2.0.
- [Release notes](https://github.com/juhaku/utoipa/releases)
- [Changelog](https://github.com/juhaku/utoipa/blob/master/utoipa-rapidoc/CHANGELOG.md)
- [Commits](https://github.com/juhaku/utoipa/compare/utoipa-5.1.3...utoipa-5.2.0)

---
updated-dependencies:
- dependency-name: utoipa
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 09:44:26 -05:00
dependabot[bot] 0e78489a56 chore(deps): bump anyhow from 1.0.91 to 1.0.92
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.91 to 1.0.92.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.91...1.0.92)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 09:44:20 -05:00
dependabot[bot] 466f6ff402 chore(deps): bump serde from 1.0.210 to 1.0.213
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.210 to 1.0.213.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.210...v1.0.213)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 10:46:29 -04:00
dependabot[bot] bd2cc980c7 chore(deps): bump thiserror from 1.0.64 to 1.0.65
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.64 to 1.0.65.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.64...1.0.65)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 10:46:24 -04:00
dependabot[bot] 955905148c chore(deps): bump utoipa from 5.1.1 to 5.1.3
Bumps [utoipa](https://github.com/juhaku/utoipa) from 5.1.1 to 5.1.3.
- [Release notes](https://github.com/juhaku/utoipa/releases)
- [Changelog](https://github.com/juhaku/utoipa/blob/master/utoipa-rapidoc/CHANGELOG.md)
- [Commits](https://github.com/juhaku/utoipa/compare/utoipa-5.1.1...utoipa-5.1.3)

---
updated-dependencies:
- dependency-name: utoipa
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 10:46:17 -04:00
dependabot[bot] b9da5ee9f6 chore(deps): bump tokio from 1.40.0 to 1.41.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.40.0 to 1.41.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.40.0...tokio-1.41.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 10:45:55 -04:00
dependabot[bot] 81d41b3cd8 chore(deps): bump bytes from 1.7.2 to 1.8.0
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.7.2 to 1.8.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.7.2...v1.8.0)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 10:45:47 -04:00
dependabot[bot] fbf29a9350 chore(deps): bump actions/setup-python from 5.2.0 to 5.3.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 09:51:15 -04:00
Dan Norris cfc7c4504a
fix(chart): reference the correct value for the Jetstream domain (#467)
Pull the `jetstreamDomain` value from `config.wadm.nats.jetstreamDomain`
instead of from `config.wadm.jetstreamDomain` since that is what we
define in the values file. It is also a more logical way to group the
value than what the chart was expecting.

Also bump the default version of wadm to the latest one.

Signed-off-by: Dan Norris <protochron@users.noreply.github.com>
2024-10-24 10:29:48 -04:00
Brooks Townsend 6f29e72932 release(wadm): 0.18, types and client 0.7
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-22 16:37:08 -04:00
dependabot[bot] 9ac409a28d chore(deps): bump anyhow from 1.0.89 to 1.0.91
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.89 to 1.0.91.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.89...1.0.91)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-22 14:28:00 -04:00
dependabot[bot] 1309c9bf1f chore(deps): bump uuid from 1.10.0 to 1.11.0
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.10.0...1.11.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-22 14:25:52 -04:00
dependabot[bot] 54740fbf62 chore(deps): bump serde_json from 1.0.128 to 1.0.132
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.128 to 1.0.132.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/1.0.128...1.0.132)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-22 14:22:09 -04:00
Joonas Bergius eb34a928c6
fix(wadm-types): Address RUSTSEC-2024-0370 (#461)
* fix(wadm-types): Address RUSTSEC-2024-0370

Signed-off-by: Joonas Bergius <joonas@cosmonic.com>

* chore: Bump wadm-types version

Signed-off-by: Joonas Bergius <joonas@cosmonic.com>

---------

Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-10-22 11:20:10 -07:00
Joonas Bergius 4d2fc1a406
chore: Swap wolfi-base source to cgr.dev instead of Docker Hub (#462)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-10-21 16:15:25 -05:00
Brooks Townsend 08da607ad9 release(wadm): v0.18.0-rc.1
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-21 17:07:14 -04:00
Brooks Townsend 9972d4d903 refactor(wadm): address clippy warnings
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Brooks Townsend b459bea3fb test(upgrades): add link name for wasmCloud 1.3
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Brooks Townsend b7ef888072 chore: prefix shared annotation with experimental
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Brooks Townsend aa2689ab36 feat(server): ensure deployed apps find shared components
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Brooks Townsend ec08ba7316 test: add invalid shared tests
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Brooks Townsend 471f07fe67 chore(wit): update bindings for shared applications
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Brooks Townsend 0dbb3d102c test: add e2e_shared integration test
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Brooks Townsend 8830527b43 feat: add status_scaler
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Brooks Townsend 434aeafbb8 feat!: support shared components and providers
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>

fix: shared components id generation

Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-18 12:49:04 -04:00
Taylor Thomas 05d5242d27 chore: Pull in slightly older version of regex
Because transitive deps suck. We need this so we can update the OCI deps
in the main host

Signed-off-by: Taylor Thomas <taylor@cosmonic.com>
2024-10-15 12:21:11 -06:00
dependabot[bot] 77c012d6d1
chore(deps): bump clap from 4.5.19 to 4.5.20 (#454)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.19 to 4.5.20.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.19...clap_complete-v4.5.20)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-14 09:06:42 -05:00
Brooks Townsend 3a066c35c6 chore(MAINTAINERS): add organizations
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-12 17:44:22 -06:00
Brooks Townsend e07481a66c chore: add MAINTAINERS.md
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-12 14:51:43 -04:00
Joonas Bergius 4b7233af2c
release: Bump wadm to 0.17.0 (#449)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-10-09 14:21:17 -05:00
Joonas Bergius e4d453fa34
release: Bump wadm-client and wadm-types to 0.6.0 (#448)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-10-09 13:34:01 -05:00
Brooks Townsend 1e2bbc2111 chore: bump 0.16.1, remove base64 dep
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-08 15:10:28 -04:00
Brooks Townsend 5fda091b50 fix(wadm): deserialize, not decode, stream status
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-08 15:10:28 -04:00
dependabot[bot] e0d4e23758 chore(deps): bump indexmap from 2.5.0 to 2.6.0
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.5.0 to 2.6.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.5.0...2.6.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 13:15:33 -04:00
dependabot[bot] 1b768f8d20 chore(deps): bump clap from 4.5.18 to 4.5.19
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.18 to 4.5.19.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.18...clap_complete-v4.5.19)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 12:44:54 -04:00
Brooks Townsend 980d8ef926 release(wadm): v0.16.0
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-10-04 11:09:39 -04:00
Victor Adossi 12880bf5e1 fix(ci): pre-pull compose images to avoid timeouts
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
2024-10-04 01:36:07 -06:00
Victor Adossi 75c45fa750 fix(tests): re-attempt connection to NATS
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
2024-10-04 01:36:07 -06:00
Victor Adossi 51692b7156 chore(deps): update for control-interface 2.2.0
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
2024-10-04 01:36:07 -06:00
Joonas Bergius eb57ec900a
release: Bump wadm client and types (#438)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-10-03 09:00:28 -05:00
Victor Adossi 78caba43e1 chore: fix lint
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
2024-10-02 14:21:49 -04:00
Victor Adossi 3e769f5708 chore(deps): update wasmcloud-control-interface to v2.1.0
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
2024-10-02 14:21:49 -04:00
dependabot[bot] e39e1f1c63 chore(deps): bump async-trait from 0.1.82 to 0.1.83
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.82 to 0.1.83.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.82...0.1.83)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 08:27:18 -04:00
dependabot[bot] 967c047f05 chore(deps): bump testcontainers from 0.22.0 to 0.23.1
Bumps [testcontainers](https://github.com/testcontainers/testcontainers-rs) from 0.22.0 to 0.23.1.
- [Release notes](https://github.com/testcontainers/testcontainers-rs/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-rs/compare/0.22.0...0.23.1)

---
updated-dependencies:
- dependency-name: testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 08:27:12 -04:00
dependabot[bot] 8724621dc0 chore(deps): bump regex from 1.10.6 to 1.11.0
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.6 to 1.11.0.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.6...1.11.0)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 08:27:06 -04:00
Victor Adossi 1d085cab07 chore(deps): update for control-interface v2.0.0
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
2024-09-30 19:50:51 -04:00
Joonas Bergius fbf06f624e
feat: Add wolfi image (#430)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-09-23 14:37:08 -05:00
Brooks Townsend f1ef62d6cd release: bump crates for release
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-09-23 15:07:04 -04:00
Brooks Townsend a5486595a2 fix(handler): backwards compat list
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-09-23 14:02:21 -04:00
dependabot[bot] 5c4094c1c7 chore(deps): bump anyhow from 1.0.87 to 1.0.89
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.87 to 1.0.89.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.87...1.0.89)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 12:47:55 -04:00
dependabot[bot] 55caf37442 chore(deps): bump nkeys from 0.4.3 to 0.4.4
Bumps [nkeys](https://github.com/wasmcloud/nkeys) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/wasmcloud/nkeys/releases)
- [Commits](https://github.com/wasmcloud/nkeys/compare/v0.4.3...v0.4.4)

---
updated-dependencies:
- dependency-name: nkeys
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 12:41:19 -04:00
dependabot[bot] 6521d4e2c4 chore(deps): bump clap from 4.5.17 to 4.5.18
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.17 to 4.5.18.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.17...clap_complete-v4.5.18)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 12:41:09 -04:00
dependabot[bot] fa51184cfc chore(deps): bump thiserror from 1.0.63 to 1.0.64
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.63 to 1.0.64.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.63...1.0.64)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 12:40:47 -04:00
dependabot[bot] eb0b2eab9b chore(deps): bump bytes from 1.7.1 to 1.7.2
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.7.1...v1.7.2)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 12:40:38 -04:00
Joonas Bergius 1136744fe6 chore: Fix up release workflow
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-09-20 18:52:49 -06:00
Joonas Bergius efe9a8a5f6
chore: Use normal cargo build on windows (#422)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-09-20 22:44:16 +00:00
Joonas Bergius ee427db054 chore: Rework release pipeline
Fixes #210

Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-09-20 13:13:42 -06:00
Joonas Bergius 5ea118e235
chore: Revise the default NATS Server address logic (#420)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-09-19 08:14:04 -05:00
Brooks Townsend 5719f0e57e feat(wadm)!: support configuring max stream bytes
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-09-17 15:30:37 -04:00
Taylor Thomas 78343c264e fix(types): Fixes validation for wasi:keyvalue
Our validation was erroneously rejecting things that were using the batch
or watch interfaces for wasi keyvalue. Also fixes some things with versioning
so we don't have to change it everywhere all the time

Signed-off-by: Taylor Thomas <taylor@cosmonic.com>
2024-09-12 17:14:30 -04:00
dependabot[bot] ee8f8ea555 chore(deps): bump clap from 4.5.16 to 4.5.17
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.16 to 4.5.17.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.16...clap_complete-v4.5.17)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 10:13:53 -04:00
dependabot[bot] 2d2320bc61 chore(deps): bump serde from 1.0.209 to 1.0.210
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.209 to 1.0.210.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.209...v1.0.210)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 10:13:43 -04:00
dependabot[bot] 71e3138355 chore(deps): bump anyhow from 1.0.86 to 1.0.87
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.86 to 1.0.87.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.86...1.0.87)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 10:13:36 -04:00
dependabot[bot] 4c31bc24c1 chore(deps): bump serde_json from 1.0.127 to 1.0.128
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.127 to 1.0.128.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/1.0.127...1.0.128)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 10:13:29 -04:00
Joonas Bergius 7aedd8ac5c
chore(chart): Bump wadm chart to default to 0.14.0 (#402)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-09-03 10:14:20 -05:00
dependabot[bot] 2b0dd9efec chore(deps): bump async-trait from 0.1.81 to 0.1.82
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.81 to 0.1.82.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.81...0.1.82)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-03 11:13:49 -04:00
dependabot[bot] c4a3c7978a chore(deps): bump testcontainers from 0.21.1 to 0.22.0
Bumps [testcontainers](https://github.com/testcontainers/testcontainers-rs) from 0.21.1 to 0.22.0.
- [Release notes](https://github.com/testcontainers/testcontainers-rs/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-rs/compare/0.21.1...0.22.0)

---
updated-dependencies:
- dependency-name: testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-03 11:13:34 -04:00
dependabot[bot] 89c9e77f6e chore(deps): bump indexmap from 2.4.0 to 2.5.0
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.4.0 to 2.5.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.4.0...2.5.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-03 11:13:14 -04:00
dependabot[bot] fd75aaa8ef chore(deps): bump actions/setup-python from 5.1.1 to 5.2.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.1.1 to 5.2.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5.1.1...v5.2.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-03 11:13:07 -04:00
dependabot[bot] c64f28dd03 chore(deps): bump tokio from 1.39.3 to 1.40.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.39.3 to 1.40.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.3...tokio-1.40.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-03 11:12:46 -04:00
Ahmed Tadde 8011f09570
fix(server): deprecate and replace model.list operation with model.get (#400)
Signed-off-by: Ahmed <ahmedtadde@gmail.com>
2024-08-29 21:42:00 -04:00
Brooks Townsend 5a22fd1258 fix(wadm): ensure custom traits are not spread or link traits
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-27 20:22:18 -04:00
dependabot[bot] b1fb8894f6 chore(deps): bump serde from 1.0.208 to 1.0.209
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.208 to 1.0.209.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.208...v1.0.209)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 10:06:37 -04:00
dependabot[bot] 2e77266224 chore(deps): bump serde_json from 1.0.125 to 1.0.127
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.125 to 1.0.127.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/1.0.125...1.0.127)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 10:06:29 -04:00
dependabot[bot] 1e2c90645d chore(deps): bump clap from 4.5.15 to 4.5.16
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.15 to 4.5.16.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.15...clap_complete-v4.5.16)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 10:06:23 -04:00
Lachlan Heywood b78d4bf1b6 chore(schema): change name and description on json schema
Signed-off-by: Lachlan Heywood <lachieh@users.noreply.github.com>
2024-08-20 11:06:33 -07:00
Brooks Townsend 524579a1f4 release(wadm): v0.14.0
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-20 07:29:17 -07:00
Brooks Townsend e339b6cae2 release(wadm-client): v0.3.0
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-20 07:29:17 -07:00
Brooks Townsend 86ce562d7f release(wadm-types): v0.3.0
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-20 07:29:17 -07:00
dependabot[bot] d30c092942 chore(deps): bump serde_json from 1.0.124 to 1.0.125
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.124 to 1.0.125.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.124...1.0.125)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 12:29:01 -07:00
dependabot[bot] aa074af58b chore(deps): bump indexmap from 2.3.0 to 2.4.0
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.3.0 to 2.4.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.3.0...2.4.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 12:28:46 -07:00
dependabot[bot] 2fc3f6974b chore(deps): bump serde from 1.0.206 to 1.0.208
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.206 to 1.0.208.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.206...v1.0.208)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 12:28:40 -07:00
dependabot[bot] b9f65ffb0a chore(deps): bump tokio from 1.39.2 to 1.39.3
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.39.2 to 1.39.3.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.2...tokio-1.39.3)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 12:28:30 -07:00
dependabot[bot] ce7c1b4bb2
chore(deps): bump testcontainers from 0.21.0 to 0.21.1 (#390)
Bumps [testcontainers](https://github.com/testcontainers/testcontainers-rs) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/testcontainers/testcontainers-rs/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-rs/compare/0.21.0...0.21.1)

---
updated-dependencies:
- dependency-name: testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 08:57:00 -05:00
Bailey Hayes b5c471ea2a chore: bump chart appVersion
Signed-off-by: Bailey Hayes <behayes2@gmail.com>
2024-08-12 18:07:25 -06:00
dependabot[bot] afc0d916e3 chore(deps): bump serde from 1.0.204 to 1.0.206
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.204 to 1.0.206.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.204...v1.0.206)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 07:27:03 -07:00
dependabot[bot] a37ab6dd95 chore(deps): bump serde_json from 1.0.122 to 1.0.124
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.122 to 1.0.124.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.122...v1.0.124)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 07:21:31 -07:00
dependabot[bot] c3d00c714d chore(deps): bump clap from 4.5.13 to 4.5.15
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.13 to 4.5.15.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.13...v4.5.15)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 07:21:17 -07:00
dependabot[bot] b9e1cc611b chore(deps): bump nkeys from 0.3.2 to 0.4.3
Bumps [nkeys](https://github.com/wasmcloud/nkeys) from 0.3.2 to 0.4.3.
- [Release notes](https://github.com/wasmcloud/nkeys/releases)
- [Commits](https://github.com/wasmcloud/nkeys/compare/v0.3.2...v0.4.3)

---
updated-dependencies:
- dependency-name: nkeys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 07:21:06 -07:00
dependabot[bot] 203a91f1e0 chore(deps): bump regex from 1.10.5 to 1.10.6
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.5 to 1.10.6.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.5...1.10.6)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 07:21:00 -07:00
Brooks Townsend 78291c79bb refactor(scaler): rename BackoffAwareScaler to BackoffWrapper
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-12 07:16:28 -07:00
Brooks Townsend 7e03d060b3 feat(scaler)!: backoff when failed event received
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-12 07:16:28 -07:00
Brooks Townsend f1237363c1 feat(scaler): implement component scale corresponding event
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-12 07:16:28 -07:00
Bailey Hayes 5def02caaf
fix(charts): align replicas value (#382)
Rendering this chart without this fix results
in an empty field for replicas.

Signed-off-by: Bailey Hayes <behayes2@gmail.com>
2024-08-12 08:59:43 -05:00
Brooks Townsend 2d6327b943 fix(scalers): put link for component as target
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-09 13:04:36 -07:00
Brooks Townsend c295cf0e33 fix(server): use backwards compatible undeployed
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-09 10:42:19 -07:00
Brooks Townsend e2764c720b fix(bindings): add waiting status
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 12:53:44 -07:00
Brooks Townsend 2b43beb831 refactor(wadm): list manifests, don't fake status
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 12:53:44 -07:00
Brooks Townsend 066e50e4eb feat(scaler): add kind and name methods
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 12:53:44 -07:00
Brooks Townsend 6f4abcf389 fix(scalers): report status properly after reconcile
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 12:53:44 -07:00
Brooks Townsend 19cbd5a44d feat(observer): observe lattice on manifest publish
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 12:53:44 -07:00
Brooks Townsend 172db98f1e feat(wadm)!: detail status per scaler
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>

correct test status checker

Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 12:53:44 -07:00
Brooks Townsend 72170e9a8e feat(scaler): add human_friendly_name method
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 12:53:44 -07:00
Brooks Townsend ffe20a6177 fix(wadm): update reaper to allow for latency
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 12:50:26 -07:00
Brooks Townsend 8d8adfe54e fix(scalers): remove scalers upon notification
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-06 11:25:56 -07:00
Brooks Townsend c6c481f930 fix(wadm): attach lattice/multitenant to consumer metadata
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-05 15:01:26 -07:00
Brooks Townsend 43ba03790a feat(wadm)!: set cleanup interval to 60s
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-05 14:55:30 -07:00
Brooks Townsend 95bdf2a6bb ci(test): ensure entire workspace builds
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-05 14:45:09 -07:00
Brooks Townsend 50d2b76213 fix(wit): update bindings to types 0.2.0
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-05 14:45:09 -07:00
Brooks Townsend 392347dfe9 feat(client)!: return name and version from deploy model
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-05 08:52:50 -07:00
Brooks Townsend ce536fbdc8 fix(#345): use cached links when req fails
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-05 08:47:06 -07:00
dependabot[bot] 05cfd3e84e chore(deps): bump clap from 4.5.11 to 4.5.13
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.11 to 4.5.13.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.11...v4.5.13)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 07:24:21 -07:00
Joonas Bergius b0212e548c
chore: Migrate more tests over to using testcontainers for setup (#369)
chore: Migrate more tests over to using testcontainers for setup

---------

Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-08-05 09:22:52 -05:00
dependabot[bot] 409f61fa74 chore(deps): bump indexmap from 2.2.6 to 2.3.0
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.2.6 to 2.3.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.2.6...2.3.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 07:21:28 -07:00
dependabot[bot] e020955fac chore(deps): bump bytes from 1.6.1 to 1.7.1
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.6.1 to 1.7.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.6.1...v1.7.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 07:19:09 -07:00
dependabot[bot] 0755307d78 chore(deps): bump base64 from 0.21.7 to 0.22.1
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.21.7 to 0.22.1.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.21.7...v0.22.1)

---
updated-dependencies:
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 07:18:54 -07:00
dependabot[bot] bb9650198d chore(deps): bump serde_json from 1.0.121 to 1.0.122
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.121 to 1.0.122.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.121...v1.0.122)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 07:18:36 -07:00
Brooks Townsend 535b5f44f9 chore: remove unused image
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-02 09:28:44 -07:00
Brooks Townsend 66f54eed4c chore: update READMEs
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-02 09:28:44 -07:00
Brooks Townsend 39a0857a4b chore: remove duplicated manifests
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-02 09:28:44 -07:00
Brooks Townsend 1dc35d584f chore: collapse test folder into tests
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-02 09:28:44 -07:00
Joonas Bergius 4e624ffd4a
chore: Replace wash up with testcontainers (#367)
* chore: Replace wash up with testcontainers

Closes #353

Signed-off-by: Joonas Bergius <joonas@cosmonic.com>

---------

Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-08-02 11:21:54 -05:00
Brooks Townsend 1febfc92d2 chore: update README to be more direct
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-02 08:45:25 -07:00
Brooks Townsend 2de7f9eca2 chore(wadm): bump to 0.13.1
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-01 11:01:08 -07:00
Brooks Townsend 0d92cd8b92 fix(wadm): react to config events
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-01 11:01:08 -07:00
Brooks Townsend cb98a9911b chore: remove beta, pin to stable version
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-08-01 08:38:48 -07:00
dependabot[bot] e4d2f569dc chore(deps): bump clap from 4.5.9 to 4.5.11
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.9 to 4.5.11.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.9...clap_complete-v4.5.11)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-31 07:19:27 -07:00
Brooks Townsend c6777e6bca feat(*): support field on secret property
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-31 07:11:55 -07:00
Brooks Townsend 4a5dcae3cf feat(server)!: ensure claimed ID is uniquely deployed
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-30 14:54:33 -04:00
Brooks Townsend 669791b685 deps: use published wasmcloud-secrets-types crate
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend 5c643f9d9b refactor(secrets): use SecretConfig type to simplify scaler
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend 7549fd3500 refactor(secrets): use wasmcloud-secrets-types crate
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend 050f4ecbc9 fix(secretscaler): correct policy type
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend 5cecde8718 fix(validation): ensure secrets link to valid policies
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend 85fb9c4ec7 tests: represent vault secret policy
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend bead045d24 chore(wadm): bump to 0.13.0-beta.1
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend 4b0d5171cb fix(secrets): correct policy type and format
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>

fix(secrets): correct policy type and format

Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend 8db4eb791f chore(wadm-types): bump to 0.2.0-beta.1
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>

types beta

Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>

types beta

Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend aa4cb16a0f chore(wadm-client): bump to 0.2.0-beta.1
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>

client beta

Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
Brooks Townsend d43e92d5c2 refactor: rename secrets source to properties
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>

fix manifest

Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
2024-07-29 11:31:37 -04:00
dependabot[bot] f597e61680 chore(deps): bump tokio from 1.38.1 to 1.39.2
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.38.1 to 1.39.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.1...tokio-1.39.2)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 10:44:33 -04:00
dependabot[bot] e0d9e2f90e chore(deps): bump serde_json from 1.0.120 to 1.0.121
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.120 to 1.0.121.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.120...v1.0.121)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 10:44:27 -04:00
172 changed files with 10717 additions and 6662 deletions

View File

@ -19,8 +19,7 @@
},
"extensions": [
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"serayuzgur.crates"
"tamasfe.even-better-toml"
]
}
},

5
.envrc Normal file
View File

@ -0,0 +1,5 @@
if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM="
fi
watch_file rust-toolchain.toml
use flake

View File

@ -0,0 +1,38 @@
name: Install and configure wkg (linux only)
inputs:
wkg-version:
description: version of wkg to install. Should be a valid tag from https://github.com/bytecodealliance/wasm-pkg-tools/releases
default: "v0.6.0"
oci-username:
description: username for oci registry
required: true
oci-password:
description: password for oci registry
required: true
runs:
using: composite
steps:
- name: Download wkg
shell: bash
run: |
curl --fail -L https://github.com/bytecodealliance/wasm-pkg-tools/releases/download/${{ inputs.wkg-version }}/wkg-x86_64-unknown-linux-gnu -o wkg
chmod +x wkg;
echo "$(realpath .)" >> "$GITHUB_PATH";
- name: Generate and set wkg config
shell: bash
env:
WKG_OCI_USERNAME: ${{ inputs.oci-username }}
WKG_OCI_PASSWORD: ${{ inputs.oci-password }}
run: |
cat << EOF > wkg-config.toml
[namespace_registries]
wasmcloud = "wasmcloud.com"
wrpc = "bytecodealliance.org"
wasi = "wasi.dev"
[registry."wasmcloud.com".oci]
auth = { username = "${WKG_OCI_USERNAME}", password = "${WKG_OCI_PASSWORD}" }
EOF
echo "WKG_CONFIG_FILE=$(realpath wkg-config.toml)" >> $GITHUB_ENV

6
.github/release.yml vendored Normal file
View File

@ -0,0 +1,6 @@
# .github/release.yml
changelog:
exclude:
authors:
- dependabot

View File

@ -2,6 +2,7 @@ name: chart
env:
HELM_VERSION: v3.14.0
CHART_TESTING_NAMESPACE: chart-testing
on:
push:
@ -12,12 +13,15 @@ on:
- 'charts/**'
- '.github/workflows/chart.yml'
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
@ -26,18 +30,18 @@ jobs:
git fetch origin main:main
- name: Set up Helm
uses: azure/setup-helm@v4
uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0
with:
version: ${{ env.HELM_VERSION }}
# Used by helm chart-testing below
- name: Set up Python
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12.2'
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.6.1
uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0
with:
version: v3.10.1
yamllint_version: 1.35.1
@ -48,7 +52,7 @@ jobs:
ct lint --config charts/wadm/ct.yaml
- name: Create kind cluster
uses: helm/kind-action@v1.10.0
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
with:
version: "v0.22.0"
@ -56,24 +60,29 @@ jobs:
run: |
helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm repo update
helm install nats nats/nats -f charts/wadm/ci/nats.yaml
helm install nats nats/nats -f charts/wadm/ci/nats.yaml --namespace ${{ env.CHART_TESTING_NAMESPACE }} --create-namespace
- name: Run chart-testing (install)
- name: Run chart-testing install / same namespace
run: |
ct install --config charts/wadm/ct.yaml
ct install --config charts/wadm/ct.yaml --namespace ${{ env.CHART_TESTING_NAMESPACE }}
- name: Run chart-testing install / across namespaces
run: |
ct install --config charts/wadm/ct.yaml --helm-extra-set-args "--set=wadm.config.nats.server=nats://nats-headless.${{ env.CHART_TESTING_NAMESPACE }}.svc.cluster.local"
publish:
if: ${{ startsWith(github.ref, 'refs/tags/chart-v') }}
runs-on: ubuntu-22.04
needs: validate
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Helm
uses: azure/setup-helm@v4
uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0
with:
version: ${{ env.HELM_VERSION }}
@ -82,7 +91,7 @@ jobs:
helm package charts/wadm -d .helm-charts
- name: Login to GHCR
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}

View File

@ -5,6 +5,9 @@ on:
branches:
- main
permissions:
contents: read
jobs:
test:
name: e2e
@ -12,37 +15,42 @@ jobs:
strategy:
fail-fast: false
matrix:
# TODO: Re-enable the multitenant and upgrades tests in followup to #247
# e2e_test: [e2e_multiple_hosts, e2e_multitenant, e2e_upgrades]
e2e_test: [e2e_multiple_hosts, e2e_upgrades]
test: [e2e_multiple_hosts, e2e_upgrades, e2e_shared]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install latest Rust stable toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
with:
toolchain: stable
components: clippy, rustfmt
# Cache: rust
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
key: 'ubuntu-22.04-rust-cache'
# If the test uses a docker compose file, pre-emptively pull images used in docker compose
- name: Pull images for test ${{ matrix.test }}
shell: bash
run: |
export DOCKER_COMPOSE_FILE=tests/docker-compose-${{ matrix.test }}.yaml;
[[ -f "$DOCKER_COMPOSE_FILE" ]] && docker compose -f $DOCKER_COMPOSE_FILE pull;
# Run e2e tests in a matrix for efficiency
- name: Run tests ${{ matrix.e2e_test }}
- name: Run tests ${{ matrix.test }}
id: test
env:
WADM_E2E_TEST: ${{ matrix.e2e_test }}
WADM_E2E_TEST: ${{ matrix.test }}
run: make test-individual-e2e
# if the previous step fails, upload logs
- name: Upload logs for debugging
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: ${{ failure() && steps.test.outcome == 'failure' }}
with:
name: e2e-logs-${{ matrix.e2e_test }}
path: ./test/e2e_log/*
name: e2e-logs-${{ matrix.test }}
path: ./tests/e2e_log/*
# Be nice and only retain the logs for 7 days
retention-days: 7

View File

@ -9,141 +9,132 @@ on:
- 'client-v*'
workflow_dispatch: # Allow manual creation of artifacts without a release
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
build:
name: build release assets
runs-on: ${{ matrix.config.os }}
runs-on: ${{ matrix.config.runnerOs }}
outputs:
version_output: ${{ steps.version_output.outputs.version }}
strategy:
matrix:
config:
# NOTE: We are building on an older version of ubuntu because of libc compatibility
# issues. Namely, if we build on a new version of libc, it isn't backwards compatible with
# old versions. But if we build on the old version, it is compatible with the newer
# versions running in ubuntu 22 and its ilk
- {
os: 'ubuntu-20.04',
arch: 'amd64',
extension: '',
targetPath: 'target/release/',
runnerOs: 'ubuntu-latest',
buildCommand: 'cargo zigbuild',
target: 'x86_64-unknown-linux-musl',
uploadArtifactSuffix: 'linux-amd64',
buildOutputPath: 'target/x86_64-unknown-linux-musl/release/wadm',
}
- {
os: 'ubuntu-20.04',
arch: 'aarch64',
extension: '',
targetPath: 'target/aarch64-unknown-linux-gnu/release/',
runnerOs: 'ubuntu-latest',
buildCommand: 'cargo zigbuild',
target: 'aarch64-unknown-linux-musl',
uploadArtifactSuffix: 'linux-aarch64',
buildOutputPath: 'target/aarch64-unknown-linux-musl/release/wadm',
}
- {
os: 'macos-13',
arch: 'amd64',
extension: '',
targetPath: 'target/release/',
runnerOs: 'macos-14',
buildCommand: 'cargo zigbuild',
target: 'x86_64-apple-darwin',
uploadArtifactSuffix: 'macos-amd64',
buildOutputPath: 'target/x86_64-apple-darwin/release/wadm',
}
- {
os: 'windows-latest',
arch: 'amd64',
extension: '.exe',
targetPath: 'target/release/',
runnerOs: 'macos-14',
buildCommand: 'cargo zigbuild',
target: 'aarch64-apple-darwin',
uploadArtifactSuffix: 'macos-aarch64',
buildOutputPath: 'target/aarch64-apple-darwin/release/wadm',
}
- {
os: 'macos-latest',
arch: 'aarch64',
extension: '',
targetPath: 'target/release/',
runnerOs: 'windows-latest',
buildCommand: 'cargo build',
target: 'x86_64-pc-windows-msvc',
uploadArtifactSuffix: 'windows-amd64',
buildOutputPath: 'target/x86_64-pc-windows-msvc/release/wadm.exe',
}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: set the release version (tag)
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
- name: set the release version (main)
if: github.ref == 'refs/heads/main'
shell: bash
run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV
if: ${{ github.ref == 'refs/heads/main' }}
run: |
echo "RELEASE_VERSION=canary" >> $GITHUB_ENV
- name: Output Version
id: version_output
run: echo "version=$RELEASE_VERSION" >> $GITHUB_OUTPUT
- name: lowercase the runner OS name
shell: bash
run: |
OS=$(echo "${{ runner.os }}" | tr '[:upper:]' '[:lower:]')
echo "RUNNER_OS=$OS" >> $GITHUB_ENV
- name: Install Zig
uses: mlugg/setup-zig@8d6198c65fb0feaa111df26e6b467fea8345e46f # v2.0.5
with:
version: 0.13.0
- name: Install latest Rust stable toolchain
uses: dtolnay/rust-toolchain@stable
if: matrix.config.arch != 'aarch64' || startsWith(matrix.config.os, 'macos')
uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
with:
toolchain: stable
components: clippy, rustfmt
target: ${{ matrix.config.target }}
- name: setup for cross-compile builds
if: matrix.config.arch == 'aarch64' && matrix.config.os == 'ubuntu-20.04'
- name: Install cargo zigbuild
uses: taiki-e/install-action@2c73a741d1544cc346e9b0af11868feba03eb69d # v2.58.9
with:
tool: cargo-zigbuild
- name: Build wadm
run: |
sudo apt-get update
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
rustup toolchain install stable-aarch64-unknown-linux-gnu
rustup target add --toolchain stable-aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
echo "CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++" >> $GITHUB_ENV
${{ matrix.config.buildCommand }} --release --bin wadm --target ${{ matrix.config.target }}
- name: Install latest Rust stable toolchain
uses: dtolnay/rust-toolchain@stable
if: matrix.config.arch == 'aarch64' && matrix.config.os == 'ubuntu-20.04'
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
toolchain: stable
components: clippy, rustfmt
target: aarch64-unknown-linux-gnu
- name: build release (amd64 linux, macos, windows)
if: matrix.config.arch != 'aarch64' || startsWith(matrix.config.os, 'macos')
run: 'cargo build --release --bin wadm'
- name: build release (arm64 linux)
if: matrix.config.arch == 'aarch64' && matrix.config.os == 'ubuntu-20.04'
run: 'cargo build --release --bin wadm --target aarch64-unknown-linux-gnu'
- uses: actions/upload-artifact@v4
with:
name: wadm-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}
name: wadm-${{ env.RELEASE_VERSION }}-${{ matrix.config.uploadArtifactSuffix }}
if-no-files-found: error
path: |
${{ matrix.config.targetPath }}wadm${{ matrix.config.extension }}
${{ matrix.config.buildOutputPath }}
publish:
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
name: publish release assets
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
env:
RELEASE_VERSION: ${{ needs.build.outputs.version_output }}
steps:
- name: download release assets
uses: actions/download-artifact@v4
- name: Generate Checksums
- name: Download release assets
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
- name: Prepare release
run: |
for dir in */; do
cd "$dir" || continue
sum=$(sha256sum * | awk '{ print $1 }')
echo "$dir:$sum" >> checksums-${{ env.RELEASE_VERSION }}.txt
cd ..
test -d "$dir" || continue
tarball="${dir%/}.tar.gz"
tar -czvf "${tarball}" "$dir"
sha256sum "${tarball}" >> SHA256SUMS
done
- name: Package Binaries
run: for dir in */; do tar -czvf "${dir%/}.tar.gz" "$dir"; done
- name: Create github release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: false
draft: false
files: |
checksums-${{ env.RELEASE_VERSION }}.txt
SHA256SUMS
wadm-${{ env.RELEASE_VERSION }}-linux-aarch64.tar.gz
wadm-${{ env.RELEASE_VERSION }}-linux-amd64.tar.gz
wadm-${{ env.RELEASE_VERSION }}-macos-aarch64.tar.gz
@ -151,35 +142,38 @@ jobs:
wadm-${{ env.RELEASE_VERSION }}-windows-amd64.tar.gz
crate:
if: ${{ startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/types-v') || startsWith(github.ref, 'refs/tags/client-v') }}
name: Publish crate
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/types-v') || startsWith(github.ref, 'refs/tags/client-v')
needs: build
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install latest Rust stable toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
with:
toolchain: stable
- name: Cargo login
run: cargo login ${{ secrets.CRATES_TOKEN }}
shell: bash
run: |
cargo login ${{ secrets.CRATES_TOKEN }}
- name: Cargo publish wadm-types
if: startsWith(github.ref, 'refs/tags/types-v')
run: cargo publish
if: ${{ startsWith(github.ref, 'refs/tags/types-v') }}
working-directory: ./crates/wadm-types
shell: bash
run: |
cargo publish
- name: Cargo publish wadm lib
if: startsWith(github.ref, 'refs/tags/v')
run: cargo publish
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
working-directory: ./crates/wadm
shell: bash
run: |
cargo publish
- name: Cargo publish wadm-client
if: startsWith(github.ref, 'refs/tags/client-v')
run: cargo publish
if: ${{ startsWith(github.ref, 'refs/tags/client-v') }}
working-directory: ./crates/wadm-client
shell: bash
run: |
cargo publish
docker-image:
name: Build and push docker images
@ -191,28 +185,32 @@ jobs:
env:
RELEASE_VERSION: ${{ needs.build.outputs.version_output }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: wadm-${{ env.RELEASE_VERSION }}-linux-aarch64
path: ./artifacts
- run: mv ./artifacts/wadm ./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-aarch64 && chmod +x ./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-aarch64
pattern: '*linux*'
- uses: actions/download-artifact@v4
with:
name: wadm-${{ env.RELEASE_VERSION }}-linux-amd64
path: ./artifacts
- run: mv ./artifacts/wadm ./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-amd64 && chmod +x ./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-amd64
- name: Prepare container artifacts
working-directory: ./artifacts
run: |
for dir in */; do
name="${dir%/}"
mv "${name}/wadm" wadm
chmod +x wadm
rmdir "${name}"
mv wadm "${name}"
done
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -222,9 +220,14 @@ jobs:
run: |
echo "OWNER=${GITHUB_REPOSITORY_OWNER,,}" >>$GITHUB_ENV
- name: Set the formatted release version for the docker tag
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
echo "RELEASE_VERSION_DOCKER_TAG=${RELEASE_VERSION#v}" >> $GITHUB_ENV
- name: Build and push (tag)
uses: docker/build-push-action@v6
if: startsWith(github.ref, 'refs/tags/v')
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
with:
push: true
platforms: linux/amd64,linux/arm64
@ -232,11 +235,30 @@ jobs:
build-args: |
BIN_ARM64=./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-aarch64
BIN_AMD64=./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-amd64
tags: ghcr.io/${{ env.OWNER }}/wadm:latest,ghcr.io/${{ env.OWNER }}/wadm:${{ env.RELEASE_VERSION }}
tags: |
ghcr.io/${{ env.OWNER }}/wadm:latest
ghcr.io/${{ env.OWNER }}/wadm:${{ env.RELEASE_VERSION }},
ghcr.io/${{ env.OWNER }}/wadm:${{ env.RELEASE_VERSION_DOCKER_TAG }}
- name: Build and push wolfi (tag)
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
with:
push: true
platforms: linux/amd64,linux/arm64
context: ./
file: ./Dockerfile.wolfi
build-args: |
BIN_ARM64=./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-aarch64
BIN_AMD64=./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-amd64
tags: |
ghcr.io/${{ env.OWNER }}/wadm:latest-wolfi
ghcr.io/${{ env.OWNER }}/wadm:${{ env.RELEASE_VERSION }}-wolfi
ghcr.io/${{ env.OWNER }}/wadm:${{ env.RELEASE_VERSION_DOCKER_TAG }}-wolfi
- name: Build and push (main)
uses: docker/build-push-action@v6
if: github.ref == 'refs/heads/main'
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
if: ${{ github.ref == 'refs/heads/main' }}
with:
push: true
platforms: linux/amd64,linux/arm64
@ -245,3 +267,16 @@ jobs:
BIN_ARM64=./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-aarch64
BIN_AMD64=./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-amd64
tags: ghcr.io/${{ env.OWNER }}/wadm:canary
- name: Build and push (main)
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
if: ${{ github.ref == 'refs/heads/main' }}
with:
push: true
platforms: linux/amd64,linux/arm64
context: ./
file: ./Dockerfile.wolfi
build-args: |
BIN_ARM64=./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-aarch64
BIN_AMD64=./artifacts/wadm-${{ env.RELEASE_VERSION }}-linux-amd64
tags: ghcr.io/${{ env.OWNER }}/wadm:canary-wolfi

73
.github/workflows/scorecard.yml vendored Normal file
View File

@ -0,0 +1,73 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '28 13 * * 3'
push:
branches: [ "main" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v3.pre.node20
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3
with:
sarif_file: results.sarif

View File

@ -5,6 +5,9 @@ on:
branches:
- main
permissions:
contents: read
jobs:
test:
name: Test
@ -12,22 +15,21 @@ jobs:
strategy:
matrix:
os: [ubuntu-22.04]
nats_version: [2.10.7]
nats_version: [2.10.22]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install latest Rust stable toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
with:
toolchain: stable
default: true
components: clippy, rustfmt
# Cache: rust
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
key: "${{ matrix.os }}-rust-cache"
key: '${{ matrix.os }}-rust-cache'
- name: Check that Wadm JSON Schema is up-to-date
shell: bash
@ -37,14 +39,30 @@ jobs:
echo 'Wadm JSON Schema is out of date. Please run `cargo run --bin wadm-schema` and commit the changes.'
exit 1
fi
- name: Install wash
uses: wasmCloud/common-actions/install-wash@main
- name: install wash
uses: taiki-e/install-action@2c73a741d1544cc346e9b0af11868feba03eb69d # v2.58.9
with:
tool: wash@0.38.0
# GH Actions doesn't currently support passing args to service containers and there is no way
# to use an environment variable to turn on jetstream for nats, so we manually start it here
- name: Start NATS
run: docker run --rm -d --name wadm-test -p 127.0.0.1:4222:4222 nats:${{ matrix.nats_version }} -js
- name: Build
run: |
cargo build --all-features --all-targets --workspace
# Make sure the wadm crate works well with feature combinations
# The above command builds the workspace and tests with no features
- name: Check wadm crate with features
run: |
cargo check -p wadm --no-default-features
cargo check -p wadm --features cli
cargo check -p wadm --features http_admin
cargo check -p wadm --features cli,http_admin
# Run all tests
- name: Run tests
run: |

View File

@ -3,28 +3,45 @@ name: wit-wasmcloud-wadm-publish
on:
push:
tags:
- 'wit-wasmcloud-wadm-v*'
- "wit-wasmcloud-wadm-v*"
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
wit
- name: Extract tag context
id: ctx
run: |
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: |
wit
.github
- name: Extract tag context
id: ctx
run: |
version=${GITHUB_REF_NAME#wit-wasmcloud-wadm-v}
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "tarball=wit-wasmcloud-wadm-${version}.tar.gz" >> "$GITHUB_OUTPUT"
echo "version is ${version}"
- name: Build
run: |
tar cvzf ${{ steps.ctx.outputs.tarball }} -C wit wadm/wit
- name: Release
uses: softprops/action-gh-release@v2
with:
files: ${{ steps.ctx.outputs.tarball }}
make_latest: "false"
- uses: ./.github/actions/configure-wkg
with:
oci-username: ${{ github.repository_owner }}
oci-password: ${{ secrets.GITHUB_TOKEN }}
- name: Build
run: wkg wit build --wit-dir wit/wadm -o package.wasm
- name: Push version-tagged WebAssembly binary to GHCR
run: wkg publish package.wasm
- name: Package tarball for release
run: |
mkdir -p release/wit
cp wit/wadm/*.wit release/wit/
tar cvzf ${{ steps.ctx.outputs.tarball }} -C release wit
- name: Release
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
with:
files: ${{ steps.ctx.outputs.tarball }}
make_latest: "false"

7
.gitignore vendored
View File

@ -1,5 +1,5 @@
/target
test/e2e_log/
tests/e2e_log/
*.dump
@ -8,4 +8,7 @@ test/e2e_log/
# Ignore IDE specific files
.idea/
.vscode/
.vscode/
.direnv/
result

3247
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,17 @@
[package]
name = "wadm-cli"
description = "wasmCloud Application Deployment Manager: A tool for running Wasm applications in wasmCloud"
version = "0.13.0"
version.workspace = true
edition = "2021"
authors = ["wasmCloud Team"]
keywords = ["webassembly", "wasmcloud", "wadm"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/wasmcloud/wadm"
default-run = "wadm"
[workspace.package]
version = "0.21.0"
[features]
default = []
@ -19,11 +23,7 @@ members = ["crates/*"]
[dependencies]
anyhow = { workspace = true }
async-nats = { workspace = true }
async-trait = { workspace = true }
clap = { workspace = true, features = ["derive", "cargo", "env"] }
futures = { workspace = true }
nkeys = { workspace = true }
# One version back to avoid clashes with 0.10 of otlp
opentelemetry = { workspace = true, features = ["rt-tokio"] }
# 0.10 to avoid protoc dep
@ -35,27 +35,28 @@ schemars = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true, features = ["log"] }
tracing-futures = { workspace = true }
tracing-opentelemetry = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter", "json"] }
wasmcloud-control-interface = { workspace = true }
wadm = { workspace = true }
wadm = { workspace = true, features = ["cli", "http_admin"] }
wadm-types = { workspace = true }
[workspace.dependencies]
anyhow = "1"
async-nats = "0.33"
async-nats = "0.39"
async-trait = "0.1"
base64 = "0.21.2"
bytes = "1"
chrono = "0.4"
clap = { version = "4", features = ["derive", "cargo", "env"] }
cloudevents-sdk = "0.7"
cloudevents-sdk = "0.8"
futures = "0.3"
http = { version = "1", default-features = false }
http-body-util = { version = "0.1", default-features = false }
hyper = { version = "1", default-features = false }
hyper-util = { version = "0.1", default-features = false }
indexmap = { version = "2", features = ["serde"] }
jsonschema = "0.17"
jsonschema = "0.29"
lazy_static = "1"
nkeys = "0.3.0"
nkeys = "0.4.5"
# One version back to avoid clashes with 0.10 of otlp
opentelemetry = { version = "0.17", features = ["rt-tokio"] }
# 0.10 to avoid protoc dep
@ -63,38 +64,45 @@ opentelemetry-otlp = { version = "0.10", features = [
"http-proto",
"reqwest-client",
] }
rand = { version = "0.8", features = ["small_rng"] }
regex = "1.9.3"
rand = { version = "0.9", features = ["small_rng"] }
# NOTE(thomastaylor312): Pinning this temporarily to 1.10 due to transitive dependency with oci
# crates that are pinned to 1.10
regex = "~1.10"
schemars = "0.8"
semver = { version = "1.0.16", features = ["serde"] }
semver = { version = "1.0.25", features = ["serde"] }
serde = "1"
serde_json = "1"
serde_yaml = "0.9"
sha2 = "0.10.2"
thiserror = "1"
sha2 = "0.10.9"
thiserror = "2"
tokio = { version = "1", default-features = false }
tracing = { version = "0.1", features = ["log"] }
tracing-futures = "0.2"
tracing-opentelemetry = { version = "0.17" }
tracing-subscriber = { version = "0.3.7", features = ["env-filter", "json"] }
ulid = { version = "1", features = ["serde"] }
utoipa = "4"
utoipa = "5"
uuid = "1"
wadm = { version = "0.13.0", path = "./crates/wadm" }
wadm-client = { version = "0.1.0", path = "./crates/wadm-client" }
wadm-types = { version = "0.1.0", path = "./crates/wadm-types" }
wasmcloud-control-interface = "1.0.0"
wit-bindgen-wrpc = { version = "0.3.7", default-features = false }
wadm = { version = "0.21", path = "./crates/wadm" }
wadm-client = { version = "0.10", path = "./crates/wadm-client" }
wadm-types = { version = "0.8", path = "./crates/wadm-types" }
wasmcloud-control-interface = "2.4.0"
wasmcloud-secrets-types = "0.5.0"
wit-bindgen-wrpc = { version = "0.9", default-features = false }
wit-bindgen = { version = "0.36.0", default-features = false }
[dev-dependencies]
base64 = { workspace = true }
async-nats = { workspace = true }
chrono = { workspace = true }
futures = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
serial_test = "3"
wadm-client = { workspace = true }
wadm-types = { workspace = true }
wasmcloud-control-interface = { workspace = true }
testcontainers = "0.25"
[build-dependencies]
schemars = { workspace = true }

17
Dockerfile.wolfi Normal file
View File

@ -0,0 +1,17 @@
FROM cgr.dev/chainguard/wolfi-base:latest AS base
FROM base AS base-amd64
ARG BIN_AMD64
ARG BIN=$BIN_AMD64
FROM base AS base-arm64
ARG BIN_ARM64
ARG BIN=$BIN_ARM64
FROM base-$TARGETARCH
# Copy application binary from disk
COPY ${BIN} /usr/local/bin/wadm
# Run the application
ENTRYPOINT ["/usr/local/bin/wadm"]

25
MAINTAINERS.md Normal file
View File

@ -0,0 +1,25 @@
# MAINTAINERS
The following individuals are responsible for reviewing code, managing issues, and ensuring the overall quality of `wadm`.
## @wasmCloud/wadm-maintainers
Name: Joonas Bergius
GitHub: @joonas
Organization: Cosmonic
Name: Dan Norris
GitHub: @protochron
Organization: Cosmonic
Name: Taylor Thomas
GitHub: @thomastaylor312
Organization: Cosmonic
Name: Ahmed Tadde
GitHub: @ahmedtadde
Organization: PreciseTarget
Name: Brooks Townsend
GitHub: @brooksmtownsend
Organization: Cosmonic

View File

@ -2,10 +2,18 @@
# wasmCloud Application Deployment Manager (wadm)
The wasmCloud Application Deployment Manager (**wadm**) enables declarative wasmCloud applications.
It's responsible for managing a set of application deployment specifications, monitoring the current
state of an entire [lattice](https://wasmcloud.com/docs/deployment/lattice/), and issuing the
appropriate lattice control commands required to close the gap between observed and desired state.
Wadm is a Wasm-native orchestrator for managing and scaling declarative wasmCloud applications.
## Responsibilities
**wadm** is powerful because it focuses on a small set of core responsibilities, making it efficient and easy to manage.
- **Manage application specifications** - Manage applications which represent _desired state_. This includes
the creation, deletion, upgrades and rollback of applications to previous versions. Application
specifications are defined using the [Open Application Model](https://oam.dev/). For more
information on wadm's specific OAM features, see our [OAM README](./oam/README.md).
- **Observe state** - Monitor wasmCloud [CloudEvents](https://wasmcloud.com/docs/reference/cloud-event-list) from all hosts in a [lattice](https://wasmcloud.com/docs/deployment/lattice/) to build the current state.
- **Reconcile with compensating commands** - When the current state doesn't match the desired state, issue commands to wasmCloud hosts in the lattice with the [control interface](https://wasmcloud.com/docs/hosts/lattice-protocols/control-interface) to reach desired state. Wadm is constantly reconciling and will react immediately to ensure applications stay deployed. For example, if a host stops, wadm will reconcile the `host_stopped` event and issue any necessary commands to start components on other available hosts.
## Using wadm
@ -15,7 +23,7 @@ appropriate lattice control commands required to close the gap between observed
You can easily run **wadm** by downloading the [`wash`](https://wasmcloud.com/docs/installation) CLI, which automatically launches wadm alongside NATS and a wasmCloud host when you run `wash up`. You can use `wash` to query, create, and deploy applications.
```
```bash
wash up -d # Start NATS, wasmCloud, and wadm in the background
```
@ -23,7 +31,7 @@ Follow the [wasmCloud quickstart](https://wasmcloud.com/docs/tour/hello-world) t
If you prefer to run **wadm** separately and/or connect to running wasmCloud hosts, you can instead opt for using the latest GitHub release artifact and executing the binary. Simply replace the latest version, your operating system, and architecture below. Please note that wadm requires a wasmCloud host version >=0.63.0
```
```bash
# Install wadm
curl -fLO https://github.com/wasmCloud/wadm/releases/download/<version>/wadm-<version>-<os>-<arch>.tar.gz
tar -xvf wadm-<version>-<os>-<arch>.tar.gz
@ -43,7 +51,7 @@ kind: Application
metadata:
name: hello-world
annotations:
description: "HTTP hello world demo"
description: 'HTTP hello world demo'
spec:
components:
- name: http-component
@ -60,7 +68,7 @@ spec:
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
image: ghcr.io/wasmcloud/http-server:0.22.0
traits:
# Link the HTTP server and set it to listen on the local machine's port 8080
- type: link
@ -69,15 +77,16 @@ spec:
namespace: wasi
package: http
interfaces: [incoming-handler]
source_config:
- name: default-http
properties:
ADDRESS: 127.0.0.1:8080
source:
config:
- name: default-http
properties:
ADDRESS: 127.0.0.1:8080
```
Then use `wash` to deploy the manifest:
```
```bash
wash app deploy hello.yaml
```
@ -85,7 +94,7 @@ wash app deploy hello.yaml
When you're done, you can use `wash` to undeploy the application:
```
```bash
wash app undeploy hello-world
```
@ -114,28 +123,15 @@ spec:
Then simply deploy the new manifest:
```
```bash
wash app deploy hello.yaml
```
Now wasmCloud is configured to automatically scale your component to 10 instances based on incoming load.
## Responsibilities
**wadm** has a very small set of responsibilities, which actually contributes to its power.
- **Manage Application Specifications** - Manage models consisting of _desired state_. This includes
the creation and deletion and _rollback_ of models to previous versions. Application
specifications are defined using the [Open Application Model](https://oam.dev/). For more
information on wadm's specific OAM features, see our [OAM README](./oam/README.md).
- **Observe State** - Monitor wasmCloud [CloudEvents](https://wasmcloud.com/docs/reference/cloud-event-list) from all hosts in a lattice to build the current state.
- **Take Compensating Actions** - When indicated, issue commands to the [lattice control
interface](https://github.com/wasmCloud/interfaces/tree/main/lattice-control) to bring about the
changes necessary to make the desired and observed state match.
## 🚧 Advanced
You can find a Docker Compose file for deploying an end-to-end multi-tenant example in the [test](https://github.com/wasmCloud/wadm/blob/main/test/docker-compose-e2e-multitenant.yaml) directory.
You can find a Docker Compose file for deploying an end-to-end multi-tenant example in the [test](https://github.com/wasmCloud/wadm/blob/main/tests/docker-compose-e2e-multitenant.yaml) directory.
In advanced use cases, **wadm** is also capable of:
@ -152,20 +148,6 @@ Interacting with **wadm** is done over NATS on the root topic `wadm.api.{prefix}
the lattice namespace prefix. For more information on this API, please consult the [wadm
Reference](https://wasmcloud.com/docs/ecosystem/wadm/).
## Known Issues/Missing functionality
As this is a new project there are some things we know are missing or buggy. A non-exhaustive list
of these can be found below:
- It is _technically_ possible as things stand right now for a race condition with manifests when a
manifest is updated/created and deleted simultaneously. In this case, one of the operations will
win and you will end up with a manifest that still exists after you delete it or a manifest that
does not exist after you create it. This is a very unlikely scenario as only one person or process
is interacting with a specific, but it is possible. If this becomes a problem for you, please let
us know and we will consider additional ways of how we can address it.
- Manifest validation is implemented, but slightly clunky. Any PRs that make this better would be
more than welcome!
## References
The wasmCloud Application Deployment Manager (**wadm**) originally came from [the autonomous lattice

3
SECURITY.md Normal file
View File

@ -0,0 +1,3 @@
# Reporting a security issue
Please refer to the [wasmCloud Security Process and Policy](https://github.com/wasmCloud/wasmCloud/blob/main/SECURITY.md) for details on how to report security issues and vulnerabilities.

View File

@ -15,10 +15,10 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: "0.2.3"
version: '0.2.10'
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v0.12.2"
appVersion: 'v0.21.0'

View File

@ -1,4 +0,0 @@
wadm:
config:
nats:
server: "nats.default.svc.cluster.local:4222"

View File

@ -36,10 +36,15 @@ Common labels
{{- define "wadm.labels" -}}
helm.sh/chart: {{ include "wadm.chart" . }}
{{ include "wadm.selectorLabels" . }}
app.kubernetes.io/component: wadm
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: wadm
{{- with .Values.additionalLabels }}
{{ . | toYaml }}
{{- end }}
{{- end }}
{{/*
@ -50,6 +55,15 @@ app.kubernetes.io/name: {{ include "wadm.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "wadm.nats.server" -}}
- name: WADM_NATS_SERVER
{{- if .Values.wadm.config.nats.server }}
value: {{ .Values.wadm.config.nats.server | quote }}
{{- else }}
value: nats-headless.{{ .Release.Namespace }}.svc.cluster.local
{{- end }}
{{- end }}
{{- define "wadm.nats.auth" -}}
{{- if .Values.wadm.config.nats.creds.secretName -}}
- name: WADM_NATS_CREDS_FILE
@ -89,4 +103,4 @@ volumes:
path: "nats.creds"
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -5,7 +5,7 @@ metadata:
labels:
{{- include "wadm.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
replicas: {{ .Values.replicas }}
selector:
matchLabels:
{{- include "wadm.selectorLabels" . | nindent 6 }}
@ -34,8 +34,7 @@ spec:
image: "{{ .Values.wadm.image.repository }}:{{ .Values.wadm.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.wadm.image.pullPolicy }}
env:
- name: WADM_NATS_SERVER
value: {{ .Values.wadm.config.nats.server | quote }}
{{- include "wadm.nats.server" . | nindent 12 }}
{{- include "wadm.nats.auth" . | nindent 12 }}
{{- if .Values.wadm.config.nats.tlsCaFile }}
- name: WADM_NATS_TLS_CA_FILE
@ -57,9 +56,9 @@ spec:
- name: WADM_TRACING_ENDPOINT
value: {{ .Values.wadm.config.tracingEndpoint | quote }}
{{- end }}
{{- if .Values.wadm.config.jetstreamDomain }}
{{- if .Values.wadm.config.nats.jetstreamDomain }}
- name: WADM_JETSTREAM_DOMAIN
value: {{ .Values.wadm.config.jetstreamDomain | quote }}
value: {{ .Values.wadm.config.nats.jetstreamDomain | quote }}
{{- end }}
{{- if .Values.wadm.config.maxJobs }}
- name: WADM_MAX_JOBS

View File

@ -14,7 +14,7 @@ wadm:
hostId: ""
logLevel: ""
nats:
server: "127.0.0.1:4222"
server: ""
jetstreamDomain: ""
tlsCaFile: ""
creds:
@ -34,6 +34,9 @@ imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
additionalLabels: {}
# app: wadm
serviceAccount:
# Specifies whether a service account should be created
create: true

View File

@ -1,7 +1,7 @@
[package]
name = "wadm-client"
description = "A client library for interacting with the wadm API"
version = "0.1.2"
version = "0.10.0"
edition = "2021"
authors = ["wasmCloud Team"]
keywords = ["webassembly", "wasmcloud", "wadm"]
@ -11,14 +11,10 @@ repository = "https://github.com/wasmcloud/wadm"
[dependencies]
anyhow = { workspace = true }
async-nats = { workspace = true }
bytes = { workspace = true }
futures = { workspace = true }
nkeys = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true, features = ["log"] }
tracing-futures = { workspace = true }
wadm-types = { workspace = true }

View File

@ -205,7 +205,13 @@ impl Client {
///
/// Please note that an OK response does not necessarily mean that the manifest was deployed
/// successfully, just that the server accepted the deployment request.
pub async fn deploy_manifest(&self, name: &str, version: Option<&str>) -> Result<()> {
///
/// Returns a tuple of the name and version of the manifest that was deployed
pub async fn deploy_manifest(
&self,
name: &str,
version: Option<&str>,
) -> Result<(String, Option<String>)> {
let topic = self.topics.model_deploy_topic(name);
let body = if let Some(version) = version {
serde_json::to_vec(&DeployModelRequest {
@ -221,7 +227,7 @@ impl Client {
match body.result {
DeployResult::Error => Err(ClientError::ApiError(body.message)),
DeployResult::NotFound => Err(ClientError::NotFound(name.to_string())),
DeployResult::Acknowledged => Ok(()),
DeployResult::Acknowledged => Ok((body.name, body.version)),
}
}
@ -243,8 +249,8 @@ impl Client {
/// Undeploys the given manifest from the lattice
///
/// Returns Ok if the manifest undeploy request was acknowledged
pub async fn undeploy_manifest(&self, name: &str) -> Result<()> {
/// Returns Ok(manifest_name) if the manifest undeploy request was acknowledged
pub async fn undeploy_manifest(&self, name: &str) -> Result<String> {
let topic = self.topics.model_undeploy_topic(name);
let resp = self
.client
@ -255,7 +261,7 @@ impl Client {
match body.result {
DeployResult::Error => Err(ClientError::ApiError(body.message)),
DeployResult::NotFound => Err(ClientError::NotFound(name.to_string())),
DeployResult::Acknowledged => Ok(()),
DeployResult::Acknowledged => Ok(body.name),
}
}

View File

@ -74,9 +74,8 @@ impl TopicGenerator {
/// Returns the full topic for WADM status subscriptions
pub fn wadm_status_topic(&self, app_name: &str) -> String {
format!(
"{}.{}.{}",
WADM_STATUS_API_PREFIX, self.topic_prefix, app_name
)
// Extract just the lattice name from topic_prefix
let lattice = self.topic_prefix.split('.').last().unwrap_or("default");
format!("{}.{}.{}", WADM_STATUS_API_PREFIX, lattice, app_name)
}
}

View File

@ -1,7 +1,7 @@
[package]
name = "wadm-types"
description = "Types and validators for the wadm API"
version = "0.1.1"
version = "0.8.3"
edition = "2021"
authors = ["wasmCloud Team"]
keywords = ["webassembly", "wasmcloud", "wadm"]
@ -9,35 +9,20 @@ license = "Apache-2.0"
repository = "https://github.com/wasmcloud/wadm"
[features]
default = []
wit = ["wit-bindgen-wrpc"]
wit = []
[dependencies]
serde_yaml = { workspace = true }
anyhow = { workspace = true }
async-nats = { workspace = true }
async-trait = { workspace = true }
base64 = { workspace = true }
bytes = { workspace = true }
chrono = { workspace = true }
cloudevents-sdk = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
jsonschema = { workspace = true }
lazy_static = { workspace = true }
nkeys = { workspace = true }
rand = { workspace = true, features = ["small_rng"] }
regex = { workspace = true }
schemars = { workspace = true }
semver = { workspace = true, features = ["serde"] }
serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true, features = ["log"] }
tracing-futures = { workspace = true }
ulid = { workspace = true, features = ["serde"] }
serde_yaml = { workspace = true }
utoipa = { workspace = true }
uuid = { workspace = true }
wasmcloud-control-interface = { workspace = true }
wit-bindgen-wrpc = { workspace = true, optional = true }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
tokio = { workspace = true, features = ["full"] }
wit-bindgen-wrpc = { workspace = true }
[target.'cfg(target_family = "wasm")'.dependencies]
wit-bindgen = { workspace = true, features = ["macros"] }

View File

@ -1,28 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: config-example
annotations:
description: 'This is my app'
spec:
components:
- name: http
type: component
properties:
image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
# You can pass any config data you'd like sent to your component as a string->string map
config:
- name: component_config
properties:
lang: EN-US
- name: webcap
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
# You can pass any config data you'd like sent to your provider as a string->string map
config:
- name: provider_config
properties:
default-port: '8080'
cache_file: '/tmp/mycache.json'

View File

@ -1,40 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: my-example-app
annotations:
description: "This is my app revision 2"
spec:
components:
- name: userinfo
type: component
properties:
image: wasmcloud.azurecr.io/fake:1
traits:
# NOTE: This demonstrates what a custom scaler could look like. This functionality does not currently exist
- type: customscaler
properties:
instances: 4
clouds:
- aws
- azure
scale_profile: mini
- name: webcap
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.13.1
traits:
- type: link
properties:
target:
name: userinfo
namespace: wasi
package: http
interfaces:
- incoming-handler
source:
config:
- name: default-port
properties:
port: "8080"

View File

@ -1,38 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: echo
annotations:
description: 'This is my app'
spec:
components:
- name: echo
type: component
properties:
image: wasmcloud.azurecr.io/echo:0.3.7
traits:
- type: spreadscaler
properties:
instances: 1
- name: httpserver
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.17.0
traits:
- type: spreadscaler
properties:
instances: 1
- type: link
properties:
target:
name: echo
namespace: wasi
package: http
interfaces:
- incoming-handler
source:
config:
- name: default-port
properties:
address: 0.0.0.0:8080

View File

@ -1,38 +0,0 @@
# Metadata
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: hello-world
annotations:
description: 'HTTP hello world demo'
spec:
components:
- name: http-component
type: component
properties:
# Run components from OCI registries as below or from a local .wasm component binary.
image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
traits:
# One replica of this component will run
- type: spreadscaler
properties:
instances: 1
# The httpserver capability provider, started from the official wasmCloud OCI artifact
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
traits:
# Link the HTTP server and set it to listen on the local machine's port 8080
- type: link
properties:
target:
name: http-component
namespace: wasi
package: http
interfaces: [incoming-handler]
source:
config:
- name: default-http
properties:
ADDRESS: 127.0.0.1:8080

View File

@ -1,60 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: kvcounter-rust
annotations:
description: 'Kvcounter demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)'
labels:
app.oam.io/name: kvcounter-rust
spec:
components:
- name: kvcounter
type: component
properties:
image: file:///Users/brooks/github.com/wasmcloud/wadm/kvc/build/http_hello_world_s.wasm
traits:
# Govern the spread/scheduling of the component
- type: spreadscaler
properties:
instances: 1
# Compose with KVRedis for wasi:keyvalue calls
- type: link
properties:
target:
name: kvredis
config:
- name: redis-connect-local
properties:
url: redis://127.0.0.1:6379
namespace: wasi
package: keyvalue
interfaces:
- atomic
- eventual
# Add a capability provider that mediates HTTP access
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
traits:
# Compose with component to handle wasi:http calls
- type: link
properties:
target:
name: kvcounter
namespace: wasi
package: http
interfaces:
- incoming-handler
source:
config:
- name: listen-config
properties:
address: 127.0.0.1:8080
# Add a capability provider that interfaces with the Redis key-value store
- name: kvredis
type: capability
properties:
image: ghcr.io/wasmcloud/keyvalue-redis:0.23.0

View File

@ -1,87 +0,0 @@
{
"apiVersion": "core.oam.dev/v1beta1",
"kind": "Application",
"metadata": {
"name": "my-example-app",
"annotations": {
"description": "This is my app"
}
},
"spec": {
"components": [
{
"name": "userinfo",
"type": "actor",
"properties": {
"image": "wasmcloud.azurecr.io/fake:1"
},
"traits": [
{
"type": "spreadscaler",
"properties": {
"instances": 4,
"spread": [
{
"name": "eastcoast",
"requirements": {
"zone": "us-east-1"
},
"weight": 80
},
{
"name": "westcoast",
"requirements": {
"zone": "us-west-1"
},
"weight": 20
}
]
}
}
]
},
{
"name": "webcap",
"type": "capability",
"properties": {
"image": "wasmcloud.azurecr.io/httpserver:0.13.1"
},
"traits": [
{
"type": "link",
"properties": {
"target": "webcap",
"namespace": "wasi",
"package": "http",
"interfaces": ["incoming-handler"],
"name": "default"
}
}
]
},
{
"name": "ledblinky",
"type": "capability",
"properties": {
"image": "wasmcloud.azurecr.io/ledblinky:0.0.1"
},
"traits": [
{
"type": "spreadscaler",
"properties": {
"instances": 1,
"spread": [
{
"name": "haslights",
"requirements": {
"ledenabled": "true"
}
}
]
}
}
]
}
]
}
}

View File

@ -1,52 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: my-example-app
annotations:
description: "This is my app"
spec:
components:
- name: userinfo
type: actor
properties:
image: wasmcloud.azurecr.io/fake:1
traits:
- type: spreadscaler
properties:
instances: 4
spread:
- name: eastcoast
requirements:
zone: us-east-1
weight: 80
- name: westcoast
requirements:
zone: us-west-1
weight: 20
- name: webcap
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.13.1
traits:
- type: link
properties:
target:
name: webcap
namespace: wasi
package: http
interfaces: ["incoming-handler"]
name: default
- name: ledblinky
type: capability
properties:
image: wasmcloud.azurecr.io/ledblinky:0.0.1
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: haslights
requirements:
ledenabled: "true"

View File

@ -1,49 +0,0 @@
# Copied from https://github.com/wasmCloud/wasmCloud/blob/main/examples/rust/components/sqldb-postgres-query/wadm.yaml
---
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: rust-sqldb-postgres-query
annotations:
version: v0.0.1
description: |
Demo WebAssembly component using the wasmCloud SQLDB Postgres provider via the wasmcloud:postgres WIT interface
wasmcloud.dev/authors: wasmCloud team
wasmcloud.dev/source-url: https://github.com/wasmCloud/wasmCloud/blob/main/examples/rust/components/sqldb-postgres-quer/wadm.yaml
wasmcloud.dev/readme-md-url: https://github.com/wasmCloud/wasmCloud/blob/main/examples/rust/components/sqldb-postgres-quer/README.md
wasmcloud.dev/homepage: https://github.com/wasmCloud/wasmCloud/tree/main/examples/rust/components/sqldb-postgres-quer
wasmcloud.dev/categories: |
database,sqldb,postgres,rust,example
spec:
components:
- name: querier
type: component
properties:
# To use the locally compiled code in this folder, use the line below instead after running `wash build`:
# image: file://./build/sqldb_postgres_query_s.wasm
image: ghcr.io/wasmcloud/components/sqldb-postgres-query-rust:0.1.0
traits:
# Govern the spread/scheduling of the component
- type: spreadscaler
properties:
instances: 1
# Establish a unidirectional link to the `sqldb-postgres` provider (the sqldb provider),
# so the `querier` component can make use of sqldb functionality provided Postgres
# (i.e. reading/writing to a database)
- type: link
properties:
target:
name: sqldb-postgres
config:
- name: default-postgres
namespace: wasmcloud
package: postgres
interfaces: [query]
# Add a capability provider that interacts with the filesystem
- name: sqldb-postgres
type: capability
properties:
image: ghcr.io/wasmcloud/sqldb-postgres:0.2.0
config:
- name: 'default-postgres'

View File

@ -23,6 +23,14 @@ pub struct GetModelResponse {
pub manifest: Option<Manifest>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ListModelsResponse {
pub result: GetResult,
#[serde(default)]
pub message: String,
pub models: Vec<ModelSummary>,
}
/// Possible outcomes of a get request
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
@ -62,7 +70,11 @@ pub struct ModelSummary {
pub version: String,
pub description: Option<String>,
pub deployed_version: Option<String>,
#[serde(default)]
pub detailed_status: Status,
#[deprecated(since = "0.14.0", note = "Use detailed_status instead")]
pub status: StatusType,
#[deprecated(since = "0.14.0", note = "Use detailed_status instead")]
pub status_message: Option<String>,
}
@ -123,6 +135,10 @@ pub struct DeployModelResponse {
pub result: DeployResult,
#[serde(default)]
pub message: String,
#[serde(default)]
pub name: String,
#[serde(default)]
pub version: Option<String>,
}
/// All possible outcomes of a deploy operation
@ -160,27 +176,46 @@ pub enum StatusResult {
}
/// The current status of a model
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
pub struct Status {
pub version: String,
#[serde(rename = "status")]
pub info: StatusInfo,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub scalers: Vec<ScalerStatus>,
#[serde(default)]
#[deprecated(since = "0.14.0")]
pub version: String,
#[serde(default)]
#[deprecated(since = "0.14.0")]
pub components: Vec<ComponentStatus>,
}
impl Status {
pub fn new(info: StatusInfo, scalers: Vec<ScalerStatus>) -> Self {
#[allow(deprecated)]
Status {
info,
scalers,
version: String::with_capacity(0),
components: Vec::with_capacity(0),
}
}
}
/// The current status of a component
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[derive(Debug, Serialize, Deserialize, Default, Clone, Eq, PartialEq)]
pub struct ComponentStatus {
pub name: String,
#[serde(rename = "type")]
pub component_type: String,
#[serde(rename = "status")]
pub info: StatusInfo,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub traits: Vec<TraitStatus>,
}
/// The current status of a trait
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[derive(Debug, Serialize, Deserialize, Default, Clone, Eq, PartialEq)]
pub struct TraitStatus {
#[serde(rename = "type")]
pub trait_type: String,
@ -188,6 +223,22 @@ pub struct TraitStatus {
pub info: StatusInfo,
}
/// The current status of a scaler
#[derive(Debug, Serialize, Deserialize, Default, Clone, Eq, PartialEq)]
pub struct ScalerStatus {
/// The id of the scaler
#[serde(default)]
pub id: String,
/// The kind of scaler
#[serde(default)]
pub kind: String,
/// The human-readable name of the scaler
#[serde(default)]
pub name: String,
#[serde(rename = "status")]
pub info: StatusInfo,
}
/// Common high-level status information
#[derive(Debug, Serialize, Deserialize, Default, Clone, Eq, PartialEq)]
pub struct StatusInfo {
@ -225,12 +276,27 @@ impl StatusInfo {
message: message.to_owned(),
}
}
pub fn waiting(message: &str) -> Self {
StatusInfo {
status_type: StatusType::Waiting,
message: message.to_owned(),
}
}
pub fn unhealthy(message: &str) -> Self {
StatusInfo {
status_type: StatusType::Unhealthy,
message: message.to_owned(),
}
}
}
/// All possible status types
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Copy, Default)]
#[serde(rename_all = "lowercase")]
pub enum StatusType {
Waiting,
#[default]
Undeployed,
#[serde(alias = "compensating")]
@ -238,6 +304,7 @@ pub enum StatusType {
#[serde(alias = "ready")]
Deployed,
Failed,
Unhealthy,
}
// Implementing add makes it easy for use to get an aggregate status by summing all of them together
@ -260,9 +327,15 @@ impl std::ops::Add for StatusType {
// If anything is undeployed, the whole thing is
(Self::Undeployed, _) => Self::Undeployed,
(_, Self::Undeployed) => Self::Undeployed,
// If anything is waiting, the whole thing is
(Self::Waiting, _) => Self::Waiting,
(_, Self::Waiting) => Self::Waiting,
(Self::Reconciling, _) => Self::Reconciling,
(_, Self::Reconciling) => Self::Reconciling,
_ => unreachable!("aggregating StatusType failure. This is programmer error"),
(Self::Unhealthy, _) => Self::Unhealthy,
(_, Self::Unhealthy) => Self::Unhealthy,
// This is technically covered in the first comparison, but we'll be explicit
(Self::Deployed, Self::Deployed) => Self::Deployed,
}
}
}
@ -328,6 +401,20 @@ mod test {
StatusType::Failed
));
assert!(matches!(
[StatusType::Deployed, StatusType::Unhealthy]
.into_iter()
.sum(),
StatusType::Unhealthy
));
assert!(matches!(
[StatusType::Reconciling, StatusType::Unhealthy]
.into_iter()
.sum(),
StatusType::Reconciling
));
let empty: Vec<StatusType> = Vec::new();
assert!(matches!(empty.into_iter().sum(), StatusType::Undeployed));
}

View File

@ -3,19 +3,43 @@ use crate::{
ComponentStatus, DeleteResult, GetResult, ModelSummary, PutResult, Status, StatusInfo,
StatusResult, StatusType, TraitStatus, VersionInfo,
},
CapabilityProperties, Component, ComponentProperties, ConfigProperty, LinkProperty, Manifest,
Metadata, Properties, Specification, Spread, SpreadScalerProperty, Trait, TraitProperty,
CapabilityProperties, Component, ComponentProperties, ConfigDefinition, ConfigProperty,
LinkProperty, Manifest, Metadata, Policy, Properties, SecretProperty, SecretSourceProperty,
SharedApplicationComponentProperties, Specification, Spread, SpreadScalerProperty,
TargetConfig, Trait, TraitProperty,
};
use wasmcloud::wadm;
#[cfg(all(feature = "wit", target_family = "wasm"))]
wit_bindgen::generate!({
path: "wit",
additional_derives: [
serde::Serialize,
serde::Deserialize,
],
with: {
"wasmcloud:wadm/types@0.2.0": generate,
"wasmcloud:wadm/client@0.2.0": generate,
"wasmcloud:wadm/handler@0.2.0": generate
}
});
#[cfg(all(feature = "wit", not(target_family = "wasm")))]
wit_bindgen_wrpc::generate!({
generate_unused_types: true,
additional_derives: [
serde::Serialize,
serde::Deserialize,
],
with: {
"wasmcloud:wadm/types@0.2.0": generate,
"wasmcloud:wadm/client@0.2.0": generate,
"wasmcloud:wadm/handler@0.2.0": generate
}
});
// Trait implementations for converting types in the API module to the generated types
impl From<Manifest> for wadm::types::OamManifest {
fn from(manifest: Manifest) -> Self {
wadm::types::OamManifest {
@ -41,6 +65,7 @@ impl From<Specification> for wadm::types::Specification {
fn from(spec: Specification) -> Self {
wadm::types::Specification {
components: spec.components.into_iter().map(|c| c.into()).collect(),
policies: spec.policies.into_iter().map(|c| c.into()).collect(),
}
}
}
@ -52,10 +77,17 @@ impl From<Component> for wadm::types::Component {
properties: component.properties.into(),
traits: component
.traits
.unwrap_or_default()
.into_iter()
.map(|t| t.into())
.collect(),
.map(|traits| traits.into_iter().map(|t| t.into()).collect()),
}
}
}
impl From<Policy> for wadm::types::Policy {
fn from(policy: Policy) -> Self {
wadm::types::Policy {
name: policy.name,
properties: policy.properties.into_iter().collect(),
type_: policy.policy_type,
}
}
}
@ -76,9 +108,11 @@ impl From<Properties> for wadm::types::Properties {
impl From<ComponentProperties> for wadm::types::ComponentProperties {
fn from(properties: ComponentProperties) -> Self {
wadm::types::ComponentProperties {
application: properties.application.map(Into::into),
image: properties.image,
id: properties.id,
config: properties.config.into_iter().map(|c| c.into()).collect(),
secrets: properties.secrets.into_iter().map(|c| c.into()).collect(),
}
}
}
@ -86,9 +120,11 @@ impl From<ComponentProperties> for wadm::types::ComponentProperties {
impl From<CapabilityProperties> for wadm::types::CapabilityProperties {
fn from(properties: CapabilityProperties) -> Self {
wadm::types::CapabilityProperties {
application: properties.application.map(Into::into),
image: properties.image,
id: properties.id,
config: properties.config.into_iter().map(|c| c.into()).collect(),
secrets: properties.secrets.into_iter().map(|c| c.into()).collect(),
}
}
}
@ -102,6 +138,37 @@ impl From<ConfigProperty> for wadm::types::ConfigProperty {
}
}
impl From<SecretProperty> for wadm::types::SecretProperty {
fn from(property: SecretProperty) -> Self {
wadm::types::SecretProperty {
name: property.name,
properties: property.properties.into(),
}
}
}
impl From<SecretSourceProperty> for wadm::types::SecretSourceProperty {
fn from(property: SecretSourceProperty) -> Self {
wadm::types::SecretSourceProperty {
policy: property.policy,
key: property.key,
field: property.field,
version: property.version,
}
}
}
impl From<SharedApplicationComponentProperties>
for wadm::types::SharedApplicationComponentProperties
{
fn from(properties: SharedApplicationComponentProperties) -> Self {
wadm::types::SharedApplicationComponentProperties {
name: properties.name,
component: properties.component,
}
}
}
impl From<Trait> for wadm::types::Trait {
fn from(trait_: Trait) -> Self {
wadm::types::Trait {
@ -126,25 +193,35 @@ impl From<TraitProperty> for wadm::types::TraitProperty {
impl From<LinkProperty> for wadm::types::LinkProperty {
fn from(property: LinkProperty) -> Self {
wadm::types::LinkProperty {
target: property.target,
source: property.source.map(|c| c.into()),
target: property.target.into(),
namespace: property.namespace,
package: property.package,
interfaces: property.interfaces,
source_config: property
.source_config
.into_iter()
.map(|c| c.into())
.collect(),
target_config: property
.target_config
.into_iter()
.map(|c| c.into())
.collect(),
name: property.name,
}
}
}
impl From<ConfigDefinition> for wadm::types::ConfigDefinition {
fn from(definition: ConfigDefinition) -> Self {
wadm::types::ConfigDefinition {
config: definition.config.into_iter().map(|c| c.into()).collect(),
secrets: definition.secrets.into_iter().map(|s| s.into()).collect(),
}
}
}
impl From<TargetConfig> for wadm::types::TargetConfig {
fn from(config: TargetConfig) -> Self {
wadm::types::TargetConfig {
name: config.name,
config: config.config.into_iter().map(|c| c.into()).collect(),
secrets: config.secrets.into_iter().map(|s| s.into()).collect(),
}
}
}
impl From<SpreadScalerProperty> for wadm::types::SpreadscalerProperty {
fn from(property: SpreadScalerProperty) -> Self {
wadm::types::SpreadscalerProperty {
@ -214,10 +291,14 @@ impl From<StatusType> for wadm::types::StatusType {
StatusType::Reconciling => wadm::types::StatusType::Reconciling,
StatusType::Deployed => wadm::types::StatusType::Deployed,
StatusType::Failed => wadm::types::StatusType::Failed,
StatusType::Waiting => wadm::types::StatusType::Waiting,
StatusType::Unhealthy => wadm::types::StatusType::Unhealthy,
}
}
}
// Trait implementations for converting generated types to the types in the API module
impl From<wadm::types::StatusType> for StatusType {
fn from(status: wadm::types::StatusType) -> Self {
match status {
@ -225,6 +306,8 @@ impl From<wadm::types::StatusType> for StatusType {
wadm::types::StatusType::Reconciling => StatusType::Reconciling,
wadm::types::StatusType::Deployed => StatusType::Deployed,
wadm::types::StatusType::Failed => StatusType::Failed,
wadm::types::StatusType::Waiting => StatusType::Waiting,
wadm::types::StatusType::Unhealthy => StatusType::Unhealthy,
}
}
}
@ -300,6 +383,7 @@ impl From<wadm::types::Specification> for Specification {
fn from(spec: wadm::types::Specification) -> Self {
Specification {
components: spec.components.into_iter().map(|c| c.into()).collect(),
policies: spec.policies.into_iter().map(|c| c.into()).collect(),
}
}
}
@ -309,7 +393,19 @@ impl From<wadm::types::Component> for Component {
Component {
name: component.name,
properties: component.properties.into(),
traits: Some(component.traits.into_iter().map(|t| t.into()).collect()), // Always wrap in Some
traits: component
.traits
.map(|traits| traits.into_iter().map(|t| t.into()).collect()),
}
}
}
impl From<wadm::types::Policy> for Policy {
fn from(policy: wadm::types::Policy) -> Self {
Policy {
name: policy.name,
properties: policy.properties.into_iter().collect(),
policy_type: policy.type_,
}
}
}
@ -331,8 +427,10 @@ impl From<wadm::types::ComponentProperties> for ComponentProperties {
fn from(properties: wadm::types::ComponentProperties) -> Self {
ComponentProperties {
image: properties.image,
application: properties.application.map(Into::into),
id: properties.id,
config: properties.config.into_iter().map(|c| c.into()).collect(),
secrets: properties.secrets.into_iter().map(|c| c.into()).collect(),
}
}
}
@ -341,8 +439,10 @@ impl From<wadm::types::CapabilityProperties> for CapabilityProperties {
fn from(properties: wadm::types::CapabilityProperties) -> Self {
CapabilityProperties {
image: properties.image,
application: properties.application.map(Into::into),
id: properties.id,
config: properties.config.into_iter().map(|c| c.into()).collect(),
secrets: properties.secrets.into_iter().map(|c| c.into()).collect(),
}
}
}
@ -356,6 +456,37 @@ impl From<wadm::types::ConfigProperty> for ConfigProperty {
}
}
impl From<wadm::types::SecretProperty> for SecretProperty {
fn from(property: wadm::types::SecretProperty) -> Self {
SecretProperty {
name: property.name,
properties: property.properties.into(),
}
}
}
impl From<wadm::types::SecretSourceProperty> for SecretSourceProperty {
fn from(property: wadm::types::SecretSourceProperty) -> Self {
SecretSourceProperty {
policy: property.policy,
key: property.key,
field: property.field,
version: property.version,
}
}
}
impl From<wadm::types::SharedApplicationComponentProperties>
for SharedApplicationComponentProperties
{
fn from(properties: wadm::types::SharedApplicationComponentProperties) -> Self {
SharedApplicationComponentProperties {
name: properties.name,
component: properties.component,
}
}
}
impl From<wadm::types::Trait> for Trait {
fn from(trait_: wadm::types::Trait) -> Self {
Trait {
@ -381,22 +512,35 @@ impl From<wadm::types::TraitProperty> for TraitProperty {
impl From<wadm::types::LinkProperty> for LinkProperty {
fn from(property: wadm::types::LinkProperty) -> Self {
#[allow(deprecated)]
LinkProperty {
target: property.target,
source: property.source.map(|c| c.into()),
target: property.target.into(),
namespace: property.namespace,
package: property.package,
interfaces: property.interfaces,
source_config: property
.source_config
.into_iter()
.map(|c| c.into())
.collect(),
target_config: property
.target_config
.into_iter()
.map(|c| c.into())
.collect(),
name: property.name,
source_config: None,
target_config: None,
}
}
}
impl From<wadm::types::ConfigDefinition> for ConfigDefinition {
fn from(definition: wadm::types::ConfigDefinition) -> Self {
ConfigDefinition {
config: definition.config.into_iter().map(|c| c.into()).collect(),
secrets: definition.secrets.into_iter().map(|s| s.into()).collect(),
}
}
}
impl From<wadm::types::TargetConfig> for TargetConfig {
fn from(config: wadm::types::TargetConfig) -> Self {
TargetConfig {
name: config.name,
config: config.config.into_iter().map(|c| c.into()).collect(),
secrets: config.secrets.into_iter().map(|s| s.into()).collect(),
}
}
}

View File

@ -1,6 +1,8 @@
use std::collections::{BTreeMap, HashMap};
use schemars::JsonSchema;
use serde::{de, Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use utoipa::ToSchema;
pub mod api;
#[cfg(feature = "wit")]
@ -23,6 +25,8 @@ pub const VERSION_ANNOTATION_KEY: &str = "version";
/// The description key, as predefined by the [OAM
/// spec](https://github.com/oam-dev/spec/blob/master/metadata.md#annotations-format)
pub const DESCRIPTION_ANNOTATION_KEY: &str = "description";
/// The annotation key for shared applications
pub const SHARED_ANNOTATION_KEY: &str = "experimental.wasmcloud.dev/shared";
/// The identifier for the builtin spreadscaler trait type
pub const SPREADSCALER_TRAIT: &str = "spreadscaler";
/// The identifier for the builtin daemonscaler trait type
@ -32,14 +36,11 @@ pub const LINK_TRAIT: &str = "link";
/// The string used for indicating a latest version. It is explicitly forbidden to use as a version
/// for a manifest
pub const LATEST_VERSION: &str = "latest";
/// The default link name
pub const DEFAULT_LINK_NAME: &str = "default";
/// The type and version of the secret reference stored as configuration. This is meant as an
/// internal marker of the format of the serialized secret and should not be referenced by
/// anything else but wadm. It is intended to help with schema upgrades in the future.
pub const SECRET_TYPE: &str = "secret-reference.wasmcloud.dev/v1alpha1";
/// An OAM manifest
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, utoipa::ToSchema, JsonSchema)]
/// Manifest file based on the Open Application Model (OAM) specification for declaratively managing wasmCloud applications
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Manifest {
/// The OAM version of the manifest
@ -71,11 +72,65 @@ impl Manifest {
.map(|v| v.as_str())
}
/// Indicates if the manifest is shared, meaning it can be used by multiple applications
pub fn shared(&self) -> bool {
self.metadata
.annotations
.get(SHARED_ANNOTATION_KEY)
.is_some_and(|v| v.parse::<bool>().unwrap_or(false))
}
/// Returns the components in the manifest
pub fn components(&self) -> impl Iterator<Item = &Component> {
self.spec.components.iter()
}
/// Helper function to find shared components that are missing from the given list of
/// deployed applications
pub fn missing_shared_components(&self, deployed_apps: &[&Manifest]) -> Vec<&Component> {
self.spec
.components
.iter()
.filter(|shared_component| {
match &shared_component.properties {
Properties::Capability {
properties:
CapabilityProperties {
image: None,
application: Some(shared_app),
..
},
}
| Properties::Component {
properties:
ComponentProperties {
image: None,
application: Some(shared_app),
..
},
} => {
if deployed_apps.iter().filter(|a| a.shared()).any(|m| {
m.metadata.name == shared_app.name
&& m.components().any(|c| {
c.name == shared_app.component
// This compares just the enum variant, not the actual properties
// For example, if we reference a shared component that's a capability,
// we want to make sure the deployed component is a capability.
&& std::mem::discriminant(&c.properties)
== std::mem::discriminant(&shared_component.properties)
})
}) {
false
} else {
true
}
}
_ => false,
}
})
.collect()
}
/// Returns only the WebAssembly components in the manifest
pub fn wasm_components(&self) -> impl Iterator<Item = &Component> {
self.components()
@ -119,7 +174,7 @@ impl Manifest {
}
/// The metadata describing the manifest
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
pub struct Metadata {
/// The name of the manifest. This must be unique per lattice
pub name: String,
@ -132,7 +187,7 @@ pub struct Metadata {
}
/// A representation of an OAM specification
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
pub struct Specification {
/// The list of components for describing an application
pub components: Vec<Component>,
@ -145,7 +200,7 @@ pub struct Specification {
}
/// A policy definition
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
pub struct Policy {
/// The name of this policy
pub name: String,
@ -157,9 +212,9 @@ pub struct Policy {
}
/// A component definition
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
// TODO: for some reason this works fine for capapilities but not components
//#[serde(deny_unknown_fields)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
// TODO: figure out why this can't be uncommented
// #[serde(deny_unknown_fields)]
pub struct Component {
/// The name of this component
pub name: String,
@ -203,10 +258,15 @@ impl Component {
};
secrets
}
/// Returns only links in the component
fn links(&self) -> impl Iterator<Item = &Trait> {
self.traits.iter().flatten().filter(|t| t.is_link())
}
}
/// Properties that can be defined for a component
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(tag = "type")]
pub enum Properties {
#[serde(rename = "component", alias = "actor")]
@ -215,11 +275,17 @@ pub enum Properties {
Capability { properties: CapabilityProperties },
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ComponentProperties {
/// The image reference to use
pub image: String,
/// The image reference to use. Required unless the component is a shared component
/// that is defined in another shared application.
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<String>,
/// Information to locate a component within a shared application. Cannot be specified
/// if the image is specified.
#[serde(skip_serializing_if = "Option::is_none")]
pub application: Option<SharedApplicationComponentProperties>,
/// The component ID to use for this component. If not supplied, it will be generated
/// as a combination of the [Metadata::name] and the image reference.
#[serde(skip_serializing_if = "Option::is_none")]
@ -234,7 +300,7 @@ pub struct ComponentProperties {
pub secrets: Vec<SecretProperty>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default, ToSchema, JsonSchema)]
pub struct ConfigDefinition {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub config: Vec<ConfigProperty>,
@ -242,83 +308,42 @@ pub struct ConfigDefinition {
pub secrets: Vec<SecretProperty>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, ToSchema, JsonSchema)]
pub struct SecretProperty {
/// The name of the secret. This is used by a reference by the component or capability to
/// get the secret value as a resource.
pub name: String,
/// The source of the secret. This indicates how to retrieve the secret value from a secrets
/// The properties of the secret that indicate how to retrieve the secret value from a secrets
/// backend and which backend to actually query.
pub source: SecretSourceProperty,
pub properties: SecretSourceProperty,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, ToSchema, JsonSchema)]
pub struct SecretSourceProperty {
/// The policy to use for retrieving the secret.
pub policy: String,
/// The key to use for retrieving the secret from the backend.
pub key: String,
/// The field to use for retrieving the secret from the backend. This is optional and can be
/// used to retrieve a specific field from a secret.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
/// The version of the secret to retrieve. If not supplied, the latest version will be used.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}
impl TryFrom<HashMap<String, String>> for SecretSourceProperty {
type Error = anyhow::Error;
// TODO should this actually just wrap serde_json?
fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
let secret_type = value
.get("type")
.ok_or_else(|| anyhow::anyhow!("Secret source must have a type"))?;
// Do we actually care? Feels like we should use a proto or something if we do since
// versioning would be a lot easier
if secret_type != SECRET_TYPE {
return Err(anyhow::anyhow!(
"Secret source type must be {}",
SECRET_TYPE
));
}
let policy = value
.get("policy")
.ok_or_else(|| anyhow::anyhow!("Secret source must have a policy"))?;
let key = value
.get("key")
.ok_or_else(|| anyhow::anyhow!("Secret source must have a key"))?;
let version = value.get("version").cloned();
Ok(Self {
policy: policy.clone(),
key: key.clone(),
version,
})
}
}
impl TryInto<HashMap<String, String>> for SecretSourceProperty {
type Error = anyhow::Error;
fn try_into(self) -> Result<HashMap<String, String>, Self::Error> {
let mut map = HashMap::new();
map.insert("type".to_string(), SECRET_TYPE.to_string());
map.insert("policy".to_string(), self.policy);
map.insert("key".to_string(), self.key);
if let Some(version) = self.version {
map.insert("version".to_string(), version);
}
Ok(map)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CapabilityProperties {
/// The image reference to use
pub image: String,
/// The image reference to use. Required unless the component is a shared component
/// that is defined in another shared application.
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<String>,
/// Information to locate a component within a shared application. Cannot be specified
/// if the image is specified.
#[serde(skip_serializing_if = "Option::is_none")]
pub application: Option<SharedApplicationComponentProperties>,
/// The component ID to use for this provider. If not supplied, it will be generated
/// as a combination of the [Metadata::name] and the image reference.
#[serde(skip_serializing_if = "Option::is_none")]
@ -333,7 +358,15 @@ pub struct CapabilityProperties {
pub secrets: Vec<SecretProperty>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
pub struct SharedApplicationComponentProperties {
/// The name of the shared application
pub name: String,
/// The name of the component in the shared application
pub component: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Trait {
/// The type of trait specified. This should be a unique string for the type of scaler. As we
@ -358,6 +391,11 @@ impl Trait {
self.trait_type == LINK_TRAIT
}
/// Check if a trait is a scaler
pub fn is_scaler(&self) -> bool {
self.trait_type == SPREADSCALER_TRAIT || self.trait_type == DAEMONSCALER_TRAIT
}
/// Helper that creates a new spreadscaler type trait with the given properties
pub fn new_spreadscaler(props: SpreadScalerProperty) -> Trait {
Trait {
@ -375,8 +413,9 @@ impl Trait {
}
/// Properties for defining traits
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum TraitProperty {
Link(LinkProperty),
SpreadScaler(SpreadScalerProperty),
@ -398,11 +437,11 @@ impl From<SpreadScalerProperty> for TraitProperty {
}
}
impl From<serde_json::Value> for TraitProperty {
fn from(value: serde_json::Value) -> Self {
Self::Custom(value)
}
}
// impl From<serde_json::Value> for TraitProperty {
// fn from(value: serde_json::Value) -> Self {
// Self::Custom(value)
// }
// }
/// Properties for the config list associated with components, providers, and links
///
@ -418,7 +457,7 @@ impl From<serde_json::Value> for TraitProperty {
///
/// Will result in two config scalers being created, one with the name `basic-kv` and one with the
/// name `default-port`. Wadm will not resolve collisions with configuration names between manifests.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ConfigProperty {
/// Name of the config to ensure exists
@ -438,7 +477,7 @@ impl PartialEq<ConfigProperty> for String {
}
/// Properties for links
#[derive(Debug, Serialize, Clone, PartialEq, Eq, JsonSchema, Default)]
#[derive(Debug, Serialize, Clone, PartialEq, Eq, ToSchema, JsonSchema, Default)]
#[serde(deny_unknown_fields)]
pub struct LinkProperty {
/// WIT namespace for the link
@ -538,7 +577,7 @@ impl<'de> Deserialize<'de> for LinkProperty {
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default, ToSchema, JsonSchema)]
pub struct TargetConfig {
/// The target this link applies to. This should be the name of a component in the manifest
pub name: String,
@ -555,7 +594,7 @@ impl PartialEq<TargetConfig> for String {
}
/// Properties for spread scalers
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SpreadScalerProperty {
/// Number of instances to spread across matching requirements
@ -567,7 +606,7 @@ pub struct SpreadScalerProperty {
}
/// Configuration for various spreading requirements
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Spread {
/// The name of this spread requirement
@ -615,13 +654,13 @@ mod test {
#[test]
fn test_oam_deserializer() {
let res = deserialize_json("./oam/simple1.json");
let res = deserialize_json("../../oam/simple1.json");
match res {
Ok(parse_results) => parse_results,
Err(error) => panic!("Error {:?}", error),
};
let res = deserialize_yaml("./oam/simple1.yaml");
let res = deserialize_yaml("../../oam/simple1.yaml");
match res {
Ok(parse_results) => parse_results,
Err(error) => panic!("Error {:?}", error),
@ -631,7 +670,7 @@ mod test {
#[test]
#[ignore] // see TODO in TraitProperty enum
fn test_custom_traits() {
let manifest = deserialize_yaml("./oam/custom.yaml").expect("Should be able to parse");
let manifest = deserialize_yaml("../../oam/custom.yaml").expect("Should be able to parse");
let component = manifest
.spec
.components
@ -649,7 +688,7 @@ mod test {
#[test]
fn test_config() {
let manifest = deserialize_yaml("./oam/config.yaml").expect("Should be able to parse");
let manifest = deserialize_yaml("../../oam/config.yaml").expect("Should be able to parse");
let props = match &manifest.spec.components[0].properties {
Properties::Component { properties } => properties,
_ => panic!("Should have found capability component"),
@ -682,7 +721,7 @@ mod test {
#[test]
fn test_component_matching() {
let manifest = deserialize_yaml("./oam/simple2.yaml").expect("Should be able to parse");
let manifest = deserialize_yaml("../../oam/simple2.yaml").expect("Should be able to parse");
assert_eq!(
manifest
.spec
@ -707,7 +746,7 @@ mod test {
#[test]
fn test_trait_matching() {
let manifest = deserialize_yaml("./oam/simple2.yaml").expect("Should be able to parse");
let manifest = deserialize_yaml("../../oam/simple2.yaml").expect("Should be able to parse");
// Validate component traits
let traits = manifest
.spec
@ -733,7 +772,7 @@ mod test {
&component.properties,
Properties::Capability {
properties: CapabilityProperties { image, .. }
} if image == "wasmcloud.azurecr.io/httpserver:0.13.1"
} if image.clone().expect("image to be present") == "wasmcloud.azurecr.io/httpserver:0.13.1"
)
})
.expect("Should find capability component")
@ -801,7 +840,8 @@ mod test {
name: "userinfo".to_string(),
properties: Properties::Component {
properties: ComponentProperties {
image: "wasmcloud.azurecr.io/fake:1".to_string(),
image: Some("wasmcloud.azurecr.io/fake:1".to_string()),
application: None,
id: None,
config: vec![],
secrets: vec![],
@ -814,7 +854,8 @@ mod test {
name: "webcap".to_string(),
properties: Properties::Capability {
properties: CapabilityProperties {
image: "wasmcloud.azurecr.io/httpserver:0.13.1".to_string(),
image: Some("wasmcloud.azurecr.io/httpserver:0.13.1".to_string()),
application: None,
id: None,
config: vec![],
secrets: vec![],
@ -842,7 +883,8 @@ mod test {
name: "ledblinky".to_string(),
properties: Properties::Capability {
properties: CapabilityProperties {
image: "wasmcloud.azurecr.io/ledblinky:0.0.1".to_string(),
image: Some("wasmcloud.azurecr.io/ledblinky:0.0.1".to_string()),
application: None,
id: None,
config: vec![],
secrets: vec![],
@ -920,7 +962,7 @@ mod test {
#[test]
fn test_deprecated_fields_not_set() {
let manifest = deserialize_yaml("./oam/simple2.yaml").expect("Should be able to parse");
let manifest = deserialize_yaml("../../oam/simple2.yaml").expect("Should be able to parse");
// Validate component traits
let traits = manifest
.spec
@ -935,7 +977,9 @@ mod test {
assert_eq!(traits.len(), 1, "Should have 1 trait");
if let TraitProperty::Link(ld) = &traits[0].properties {
assert_eq!(ld.source.as_ref().unwrap().config, vec![]);
assert_eq!(ld.source_config, None);
#[allow(deprecated)]
let source_config = &ld.source_config;
assert_eq!(source_config, &None);
} else {
panic!("trait property was not a link definition");
};

View File

@ -2,6 +2,7 @@
//!
use std::collections::{HashMap, HashSet};
#[cfg(not(target_family = "wasm"))]
use std::path::Path;
use std::sync::OnceLock;
@ -11,7 +12,7 @@ use serde::{Deserialize, Serialize};
use crate::{
CapabilityProperties, ComponentProperties, LinkProperty, Manifest, Properties, Trait,
TraitProperty, LATEST_VERSION,
TraitProperty, DEFAULT_LINK_NAME, LATEST_VERSION,
};
/// A namespace -> package -> interface lookup
@ -24,6 +25,8 @@ type KnownInterfaceLookup = HashMap<String, HashMap<String, HashMap<String, ()>>
/// a known namespace and package, interfaces should generally be well known.
static KNOWN_INTERFACE_LOOKUP: OnceLock<KnownInterfaceLookup> = OnceLock::new();
const SECRET_POLICY_TYPE: &str = "policy.secret.wasmcloud.dev/v1alpha1";
/// Get the static list of known interfaces
fn get_known_interface_lookup() -> &'static KnownInterfaceLookup {
KNOWN_INTERFACE_LOOKUP.get_or_init(|| {
@ -58,7 +61,12 @@ fn get_known_interface_lookup() -> &'static KnownInterfaceLookup {
("config".into(), HashMap::from([("runtime".into(), ())])),
(
"keyvalue".into(),
HashMap::from([("atomics".into(), ()), ("store".into(), ())]),
HashMap::from([
("atomics".into(), ()),
("store".into(), ()),
("batch".into(), ()),
("watch".into(), ()),
]),
),
(
"http".into(),
@ -152,9 +160,10 @@ fn is_invalid_known_interface(
};
// Unknown interface inside known namespace and package is probably a bug
if !iface_lookup.contains_key(interface) {
// Unknown package inside a known interface we control is probably a bug
// Unknown package inside a known interface we control is probably a bug, but may be
// a new interface we don't know about yet
return vec![ValidationFailure::new(
ValidationFailureLevel::Error,
ValidationFailureLevel::Warning,
format!("unrecognized interface [{namespace}:{package}/{interface}]"),
)];
}
@ -266,6 +275,7 @@ impl ValidationOutput for Vec<ValidationFailure> {
/// # Arguments
///
/// * `path` - Path to the Manifest that will be read into memory and validated
#[cfg(not(target_family = "wasm"))]
pub async fn validate_manifest_file(
path: impl AsRef<Path>,
) -> Result<(Manifest, Vec<ValidationFailure>)> {
@ -289,9 +299,12 @@ pub async fn validate_manifest_file(
pub async fn validate_manifest_bytes(
content: impl AsRef<[u8]>,
) -> Result<(Manifest, Vec<ValidationFailure>)> {
let raw_yaml_content = content.as_ref();
let manifest =
serde_yaml::from_slice(content.as_ref()).context("failed to parse manifest content")?;
let failures = validate_manifest(&manifest).await?;
let mut failures = validate_manifest(&manifest).await?;
let mut yaml_issues = validate_raw_yaml(raw_yaml_content)?;
failures.append(&mut yaml_issues);
Ok((manifest, failures))
}
@ -332,7 +345,19 @@ pub async fn validate_manifest(manifest: &Manifest) -> Result<Vec<ValidationFail
failures.extend(core_validation(manifest));
failures.extend(check_misnamed_interfaces(manifest));
failures.extend(check_dangling_links(manifest));
failures.extend(check_secrets_mapped_to_policies(manifest));
failures.extend(validate_policies(manifest));
failures.extend(ensure_no_custom_traits(manifest));
failures.extend(validate_component_properties(manifest));
failures.extend(check_duplicate_links(manifest));
failures.extend(validate_link_configs(manifest));
Ok(failures)
}
pub fn validate_raw_yaml(content: &[u8]) -> Result<Vec<ValidationFailure>> {
let mut failures = Vec::new();
let raw_content: serde_yaml::Value =
serde_yaml::from_slice(content).context("failed read raw yaml content")?;
failures.extend(validate_components_configs(&raw_content));
Ok(failures)
}
@ -459,6 +484,32 @@ fn check_misnamed_interfaces(manifest: &Manifest) -> Vec<ValidationFailure> {
failures
}
/// This validation rule should eventually be removed, but at this time (as of wadm 0.14.0)
/// custom traits are not supported. We technically deserialize the custom trait, but 99%
/// of the time this is just a poorly formatted spread or link scaler which is incredibly
/// frustrating to debug.
fn ensure_no_custom_traits(manifest: &Manifest) -> Vec<ValidationFailure> {
let mut failures = Vec::new();
for component in manifest.components() {
if let Some(traits) = &component.traits {
for trait_item in traits {
match &trait_item.properties {
TraitProperty::Custom(trt) if trait_item.is_link() => failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!("Link trait deserialized as custom trait, ensure fields are correct: {}", trt),
)),
TraitProperty::Custom(trt) if trait_item.is_scaler() => failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!("Scaler trait deserialized as custom trait, ensure fields are correct: {}", trt),
)),
_ => (),
}
}
}
}
failures
}
/// Check for "dangling" links, which contain targets that are not specified elsewhere in the
/// WADM manifest.
///
@ -520,31 +571,216 @@ fn check_dangling_links(manifest: &Manifest) -> Vec<ValidationFailure> {
failures
}
fn check_secrets_mapped_to_policies(manifest: &Manifest) -> Vec<ValidationFailure> {
/// Ensure that a manifest has secrets that are mapped to known policies
/// and that those policies have the expected type and properties.
fn validate_policies(manifest: &Manifest) -> Vec<ValidationFailure> {
let policies = manifest.policy_lookup();
let mut failures = Vec::new();
for c in manifest.components() {
// Ensure policies meant for secrets are valid
for secret in c.secrets() {
if !policies.contains_key(&secret.source.policy) {
failures.push(ValidationFailure::new(
match policies.get(&secret.properties.policy) {
Some(policy) if policy.policy_type != SECRET_POLICY_TYPE => {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!(
"secret '{}' is mapped to policy '{}' which is not a secret policy. Expected type '{SECRET_POLICY_TYPE}'",
secret.name, secret.properties.policy
),
))
}
Some(policy) => {
if !policy.properties.contains_key("backend") {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!(
"secret '{}' is mapped to policy '{}' which does not include a 'backend' property",
secret.name, secret.properties.policy
),
))
}
}
None => failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!(
"secret '{}' is mapped to unknown policy '{}'",
secret.name, secret.source.policy
secret.name, secret.properties.policy
),
))
)),
}
if !policies[&secret.source.policy]
.properties
.contains_key("backend")
{
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!(
"secret '{}' is mapped to policy '{}' which does not include a 'backend' property",
secret.name, secret.source.policy
),
))
}
}
failures
}
/// Ensure that all components in a manifest either specify an image reference or a shared
/// component in a different manifest. Note that this does not validate that the image reference
/// is valid or that the shared component is valid, only that one of the two properties is set.
pub fn validate_component_properties(application: &Manifest) -> Vec<ValidationFailure> {
let mut failures = Vec::new();
for component in application.spec.components.iter() {
match &component.properties {
Properties::Component {
properties:
ComponentProperties {
image,
application,
config,
secrets,
..
},
}
| Properties::Capability {
properties:
CapabilityProperties {
image,
application,
config,
secrets,
..
},
} => match (image, application) {
(Some(_), Some(_)) => {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
"Component cannot have both 'image' and 'application' properties".into(),
));
}
(None, None) => {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
"Component must have either 'image' or 'application' property".into(),
));
}
// This is a problem because of our left-folding config implementation. A shared application
// could specify additional config and actually overwrite the original manifest's config.
(None, Some(shared_properties)) if !config.is_empty() => {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!(
"Shared component '{}' cannot specify additional 'config'",
shared_properties.name
),
));
}
(None, Some(shared_properties)) if !secrets.is_empty() => {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!(
"Shared component '{}' cannot specify additional 'secrets'",
shared_properties.name
),
));
}
// Shared application components already have scale properties defined in their original manifest
(None, Some(shared_properties))
if component
.traits
.as_ref()
.is_some_and(|traits| traits.iter().any(|trt| trt.is_scaler())) =>
{
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!(
"Shared component '{}' cannot include a scaler trait",
shared_properties.name
),
));
}
_ => {}
},
}
}
failures
}
/// Validates link configs in a WADM application manifest.
///
/// At present this can check for:
/// - all configs that declare `properties` have unique names
/// (configs without properties refer to existing configs)
///
pub fn validate_link_configs(manifest: &Manifest) -> Vec<ValidationFailure> {
let mut failures = Vec::new();
let mut link_config_names = HashSet::new();
for link_trait in manifest.links() {
if let TraitProperty::Link(LinkProperty { target, source, .. }) = &link_trait.properties {
for config in &target.config {
// we only need to check for uniqueness of configs with properties
if config.properties.is_none() {
continue;
}
// Check if config name is unique
if !link_config_names.insert(config.name.clone()) {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!("Duplicate link config name found: '{}'", config.name),
));
}
}
if let Some(source) = source {
for config in &source.config {
// we only need to check for uniqueness of configs with properties
if config.properties.is_none() {
continue;
}
// Check if config name is unique
if !link_config_names.insert(config.name.clone()) {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!("Duplicate link config name found: '{}'", config.name),
));
}
}
}
}
}
failures
}
/// Funtion to validate the component configs
/// from 0.13.0 source_config is deprecated and replaced with source:config:
/// this function validates the raw yaml to check for deprecated source_config and target_config
pub fn validate_components_configs(application: &serde_yaml::Value) -> Vec<ValidationFailure> {
let mut failures = Vec::new();
if let Some(specs) = application.get("spec") {
if let Some(components) = specs.get("components") {
if let Some(components_sequence) = components.as_sequence() {
for component in components_sequence.iter() {
failures.extend(get_deprecated_configs(component));
}
}
}
}
failures
}
fn get_deprecated_configs(component: &serde_yaml::Value) -> Vec<ValidationFailure> {
let mut failures = vec![];
if let Some(traits) = component.get("traits") {
if let Some(traits_sequence) = traits.as_sequence() {
for trait_ in traits_sequence.iter() {
if let Some(trait_type) = trait_.get("type") {
if trait_type.ne("link") {
continue;
}
}
if let Some(trait_properties) = trait_.get("properties") {
if trait_properties.get("source_config").is_some() {
failures.push(ValidationFailure {
level: ValidationFailureLevel::Warning,
msg: "one of the components' link trait contains a source_config key, please use source:config: rather".to_string(),
});
}
if trait_properties.get("target_config").is_some() {
failures.push(ValidationFailure {
level: ValidationFailureLevel::Warning,
msg: "one of the components' link trait contains a target_config key, please use target:config: rather".to_string(),
});
}
}
}
}
}
@ -595,6 +831,51 @@ pub fn is_valid_label_name(name: &str) -> bool {
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
}
/// Checks whether a manifest contains "duplicate" links.
///
/// Multiple links from the same source with the same name, namespace, package and interface
/// are considered duplicate links.
fn check_duplicate_links(manifest: &Manifest) -> Vec<ValidationFailure> {
let mut failures = Vec::new();
for component in manifest.components() {
let mut link_ids = HashSet::new();
for link in component.links() {
if let TraitProperty::Link(LinkProperty {
name,
namespace,
package,
interfaces,
..
}) = &link.properties
{
for interface in interfaces {
if !link_ids.insert((
name.clone()
.unwrap_or_else(|| DEFAULT_LINK_NAME.to_string()),
namespace,
package,
interface,
)) {
failures.push(ValidationFailure::new(
ValidationFailureLevel::Error,
format!(
"Duplicate link found inside component '{}': {} ({}:{}/{})",
component.name,
name.clone()
.unwrap_or_else(|| DEFAULT_LINK_NAME.to_string()),
namespace,
package,
interface
),
));
};
}
}
}
}
failures
}
#[cfg(test)]
mod tests {
use super::is_valid_manifest_name;

View File

@ -1,4 +1,4 @@
[wadm]
path = "../../../wit/wadm"
sha256 = "3a7cd5d4f4bff4279330c3dfbb5dc282ee84fa03fd12b16d7a14c11feae8e7b0"
sha512 = "b80e5c44367398b40e85d418a9ce6f14db66f6c64b70eaa8029960b923e73bc023d423b884cfeb797122a29b195c2a4d67d1896a80d3d271246f0b379ddaaf2a"
sha256 = "9795ab1a83023da07da2dc28d930004bd913b9dbf07d68d9ef9207a44348a169"
sha512 = "9a94f33fd861912c81efd441cd19cc8066dbb2df5c2236d0472b66294bddc20ec5ad569484be18334d8c104ae9647b2c81c9878210ac35694ad8ba4a5b3780be"

View File

@ -1,4 +1,4 @@
package wasmcloud:wadm@0.1.0;
package wasmcloud:wadm@0.2.0;
/// A Wadm client which interacts with the wadm api
interface client {
@ -11,7 +11,7 @@ interface client {
// Deploys a model to the WADM system.
// If no lattice is provided, the default lattice name 'default' is used.
deploy-model: func(model-name: string, version: option<string>, lattice: option<string>) -> result<_, string>;
deploy-model: func(model-name: string, version: option<string>, lattice: option<string>) -> result<string, string>;
// Undeploys a model from the WADM system.
undeploy-model: func(model-name: string, lattice: option<string>, non-destructive: bool) -> result<_, string>;

View File

@ -1,7 +1,6 @@
package wasmcloud:wadm@0.1.0;
package wasmcloud:wadm@0.2.0;
interface types {
record model-summary {
name: string,
version: string,
@ -72,7 +71,9 @@ interface types {
undeployed,
reconciling,
deployed,
failed
failed,
waiting,
unhealthy
}
enum deploy-result {
@ -99,13 +100,14 @@ interface types {
// The specification for this manifest
record specification {
components: list<component>,
policies: list<policy>
}
// A component definition
record component {
name: string,
properties: properties,
traits: list<trait>,
traits: option<list<trait>>,
}
// Properties that can be defined for a component
@ -116,22 +118,27 @@ interface types {
// Properties for a component
record component-properties {
image: string,
image: option<string>,
application: option<shared-application-component-properties>,
id: option<string>,
config: list<config-property>,
secrets: list<secret-property>,
}
// Properties for a capability
record capability-properties {
image: string,
image: option<string>,
application: option<shared-application-component-properties>,
id: option<string>,
config: list<config-property>,
secrets: list<secret-property>,
}
// Properties for the config list associated with components, providers, and links
record config-property {
// A policy definition
record policy {
name: string,
properties: option<list<tuple<string, string>>>,
properties: list<tuple<string, string>>,
%type: string,
}
// A trait definition
@ -149,15 +156,53 @@ interface types {
// Properties for links
record link-property {
target: string,
namespace: string,
%package: string,
interfaces: list<string>,
source-config: list<config-property>,
target-config: list<config-property>,
source: option<config-definition>,
target: target-config,
name: option<string>,
}
// Configuration definition
record config-definition {
config: list<config-property>,
secrets: list<secret-property>,
}
// Configuration properties
record config-property {
name: string,
properties: option<list<tuple<string, string>>>,
}
// Secret properties
record secret-property {
name: string,
properties: secret-source-property,
}
// Secret source properties
record secret-source-property {
policy: string,
key: string,
field: option<string>,
version: option<string>,
}
// Shared application component properties
record shared-application-component-properties {
name: string,
component: string
}
// Target configuration
record target-config {
name: string,
config: list<config-property>,
secrets: list<secret-property>,
}
// Properties for spread scalers
record spreadscaler-property {
instances: u32,

View File

@ -1,7 +1,7 @@
package wasmcloud:wadm-types@0.1.0;
package wasmcloud:wadm-types@0.2.0;
world interfaces {
import wasmcloud:wadm/types@0.1.0;
import wasmcloud:wadm/client@0.1.0;
import wasmcloud:wadm/handler@0.1.0;
import wasmcloud:wadm/types@0.2.0;
import wasmcloud:wadm/client@0.2.0;
import wasmcloud:wadm/handler@0.2.0;
}

View File

@ -1,7 +1,7 @@
[package]
name = "wadm"
description = "wasmCloud Application Deployment Manager: A tool for running Wasm applications in wasmCloud"
version = "0.13.0"
version.workspace = true
edition = "2021"
authors = ["wasmCloud Team"]
keywords = ["webassembly", "wasmcloud", "wadm"]
@ -9,21 +9,29 @@ license = "Apache-2.0"
readme = "../../README.md"
repository = "https://github.com/wasmcloud/wadm"
[features]
# Enables clap attributes on the wadm configuration struct
cli = ["clap"]
http_admin = ["http", "http-body-util", "hyper", "hyper-util"]
default = []
[package.metadata.cargo-machete]
ignored = ["cloudevents-sdk"]
[dependencies]
anyhow = { workspace = true }
async-nats = { workspace = true }
async-trait = { workspace = true }
base64 = { workspace = true }
bytes = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, optional = true, features = ["derive", "cargo", "env"]}
cloudevents-sdk = { workspace = true }
http = { workspace = true, features = ["std"], optional = true }
http-body-util = { workspace = true, optional = true }
hyper = { workspace = true, optional = true }
hyper-util = { workspace = true, features = ["server"], optional = true }
futures = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
jsonschema = { workspace = true }
lazy_static = { workspace = true }
nkeys = { workspace = true }
rand = { workspace = true, features = ["small_rng"] }
regex = { workspace = true }
semver = { workspace = true, features = ["serde"] }
serde = { workspace = true }
serde_json = { workspace = true }
@ -37,6 +45,7 @@ ulid = { workspace = true, features = ["serde"] }
uuid = { workspace = true }
wadm-types = { workspace = true }
wasmcloud-control-interface = { workspace = true }
wasmcloud-secrets-types = { workspace = true }
[dev-dependencies]
serial_test = "3"

View File

@ -1,28 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: config-example
annotations:
description: 'This is my app'
spec:
components:
- name: http
type: component
properties:
image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
# You can pass any config data you'd like sent to your component as a string->string map
config:
- name: component_config
properties:
lang: EN-US
- name: webcap
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
# You can pass any config data you'd like sent to your provider as a string->string map
config:
- name: provider_config
properties:
default-port: '8080'
cache_file: '/tmp/mycache.json'

View File

@ -1,40 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: my-example-app
annotations:
description: "This is my app revision 2"
spec:
components:
- name: userinfo
type: component
properties:
image: wasmcloud.azurecr.io/fake:1
traits:
# NOTE: This demonstrates what a custom scaler could look like. This functionality does not currently exist
- type: customscaler
properties:
instances: 4
clouds:
- aws
- azure
scale_profile: mini
- name: webcap
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.13.1
traits:
- type: link
properties:
target:
name: userinfo
namespace: wasi
package: http
interfaces:
- incoming-handler
source:
config:
- name: default-port
properties:
port: "8080"

View File

@ -1,38 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: echo
annotations:
description: 'This is my app'
spec:
components:
- name: echo
type: component
properties:
image: wasmcloud.azurecr.io/echo:0.3.7
traits:
- type: spreadscaler
properties:
instances: 1
- name: httpserver
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.17.0
traits:
- type: spreadscaler
properties:
instances: 1
- type: link
properties:
target:
name: echo
namespace: wasi
package: http
interfaces:
- incoming-handler
source:
config:
- name: default-port
properties:
address: 0.0.0.0:8080

View File

@ -1,38 +0,0 @@
# Metadata
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: hello-world
annotations:
description: 'HTTP hello world demo'
spec:
components:
- name: http-component
type: component
properties:
# Run components from OCI registries as below or from a local .wasm component binary.
image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
traits:
# One replica of this component will run
- type: spreadscaler
properties:
instances: 1
# The httpserver capability provider, started from the official wasmCloud OCI artifact
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
traits:
# Link the HTTP server and set it to listen on the local machine's port 8080
- type: link
properties:
target:
name: http-component
namespace: wasi
package: http
interfaces: [incoming-handler]
source:
config:
- name: default-http
properties:
ADDRESS: 127.0.0.1:8080

View File

@ -1,60 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: kvcounter-rust
annotations:
description: 'Kvcounter demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)'
labels:
app.oam.io/name: kvcounter-rust
spec:
components:
- name: kvcounter
type: component
properties:
image: file:///Users/brooks/github.com/wasmcloud/wadm/kvc/build/http_hello_world_s.wasm
traits:
# Govern the spread/scheduling of the component
- type: spreadscaler
properties:
instances: 1
# Compose with KVRedis for wasi:keyvalue calls
- type: link
properties:
target:
name: kvredis
config:
- name: redis-connect-local
properties:
url: redis://127.0.0.1:6379
namespace: wasi
package: keyvalue
interfaces:
- atomic
- eventual
# Add a capability provider that mediates HTTP access
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
traits:
# Compose with component to handle wasi:http calls
- type: link
properties:
target:
name: kvcounter
namespace: wasi
package: http
interfaces:
- incoming-handler
source:
config:
- name: listen-config
properties:
address: 127.0.0.1:8080
# Add a capability provider that interfaces with the Redis key-value store
- name: kvredis
type: capability
properties:
image: ghcr.io/wasmcloud/keyvalue-redis:0.23.0

View File

@ -1,52 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: my-example-app
annotations:
description: "This is my app"
spec:
components:
- name: userinfo
type: actor
properties:
image: wasmcloud.azurecr.io/fake:1
traits:
- type: spreadscaler
properties:
instances: 4
spread:
- name: eastcoast
requirements:
zone: us-east-1
weight: 80
- name: westcoast
requirements:
zone: us-west-1
weight: 20
- name: webcap
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.13.1
traits:
- type: link
properties:
target:
name: webcap
namespace: wasi
package: http
interfaces: ["incoming-handler"]
name: default
- name: ledblinky
type: capability
properties:
image: wasmcloud.azurecr.io/ledblinky:0.0.1
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: haslights
requirements:
ledenabled: "true"

View File

@ -1,55 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: my-example-app
annotations:
description: "This is my app revision 2"
spec:
components:
- name: userinfo
type: component
properties:
image: wasmcloud.azurecr.io/fake:1
traits:
- type: spreadscaler
properties:
instances: 4
spread:
- name: eastcoast
requirements:
zone: us-east-1
weight: 80
- name: westcoast
requirements:
zone: us-west-1
weight: 20
- name: webcap
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.13.1
traits:
- type: link
properties:
target:
name: userinfo
config: []
namespace: wasi
package: http
interfaces:
- incoming-handler
source:
config: []
- name: ledblinky
type: capability
properties:
image: wasmcloud.azurecr.io/ledblinky:0.0.1
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: haslights
requirements:
ledenabled: "true"

View File

@ -1,49 +0,0 @@
# Copied from https://github.com/wasmCloud/wasmCloud/blob/main/examples/rust/components/sqldb-postgres-query/wadm.yaml
---
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: rust-sqldb-postgres-query
annotations:
version: v0.0.1
description: |
Demo WebAssembly component using the wasmCloud SQLDB Postgres provider via the wasmcloud:postgres WIT interface
wasmcloud.dev/authors: wasmCloud team
wasmcloud.dev/source-url: https://github.com/wasmCloud/wasmCloud/blob/main/examples/rust/components/sqldb-postgres-quer/wadm.yaml
wasmcloud.dev/readme-md-url: https://github.com/wasmCloud/wasmCloud/blob/main/examples/rust/components/sqldb-postgres-quer/README.md
wasmcloud.dev/homepage: https://github.com/wasmCloud/wasmCloud/tree/main/examples/rust/components/sqldb-postgres-quer
wasmcloud.dev/categories: |
database,sqldb,postgres,rust,example
spec:
components:
- name: querier
type: component
properties:
# To use the locally compiled code in this folder, use the line below instead after running `wash build`:
# image: file://./build/sqldb_postgres_query_s.wasm
image: ghcr.io/wasmcloud/components/sqldb-postgres-query-rust:0.1.0
traits:
# Govern the spread/scheduling of the component
- type: spreadscaler
properties:
instances: 1
# Establish a unidirectional link to the `sqldb-postgres` provider (the sqldb provider),
# so the `querier` component can make use of sqldb functionality provided Postgres
# (i.e. reading/writing to a database)
- type: link
properties:
target:
name: sqldb-postgres
config:
- name: default-postgres
namespace: wasmcloud
package: postgres
interfaces: [query]
# Add a capability provider that interacts with the filesystem
- name: sqldb-postgres
type: capability
properties:
image: ghcr.io/wasmcloud/sqldb-postgres:0.2.0
config:
- name: 'default-postgres'

View File

@ -2,14 +2,15 @@
use std::{
collections::{BTreeMap, HashMap},
error::Error,
hash::{Hash, Hasher},
};
use serde::{Deserialize, Serialize};
use wasmcloud_control_interface::InterfaceLinkDefinition;
use wasmcloud_control_interface::Link;
use crate::{
events::{Event, ProviderStartFailed, ProviderStarted},
events::{ComponentScaleFailed, ComponentScaled, Event, ProviderStartFailed, ProviderStarted},
workers::insert_managed_annotations,
};
@ -44,13 +45,14 @@ impl Command {
/// # Return
/// - The first element in the tuple corresponds to the "success" event a host would output after completing this command
/// - The second element in the tuple corresponds to an optional "failure" event that a host could output if processing fails
pub fn corresponding_event(&self, model_name: &str) -> Option<(Event, Option<Event>)> {
pub fn corresponding_event(&self) -> Option<(Event, Option<Event>)> {
match self {
Command::StartProvider(StartProvider {
annotations,
reference,
host_id,
provider_id,
model_name,
..
}) => {
let mut annotations = annotations.to_owned();
@ -72,6 +74,39 @@ impl Command {
})),
))
}
Command::ScaleComponent(ScaleComponent {
component_id,
host_id,
count,
reference,
annotations,
model_name,
..
}) => {
let mut annotations = annotations.to_owned();
insert_managed_annotations(&mut annotations, model_name);
Some((
Event::ComponentScaled(ComponentScaled {
component_id: component_id.to_owned(),
host_id: host_id.to_owned(),
max_instances: *count as usize,
image_ref: reference.to_owned(),
annotations: annotations.to_owned(),
// We don't know this field from the command
claims: None,
}),
Some(Event::ComponentScaleFailed(ComponentScaleFailed {
component_id: component_id.to_owned(),
host_id: host_id.to_owned(),
max_instances: *count as usize,
image_ref: reference.to_owned(),
annotations: annotations.to_owned(),
// We don't know these fields from the command
error: String::with_capacity(0),
claims: None,
})),
))
}
_ => None,
}
}
@ -201,18 +236,20 @@ pub struct PutLink {
pub model_name: String,
}
impl From<PutLink> for InterfaceLinkDefinition {
fn from(value: PutLink) -> InterfaceLinkDefinition {
InterfaceLinkDefinition {
source_id: value.source_id,
target: value.target,
name: value.name,
wit_namespace: value.wit_namespace,
wit_package: value.wit_package,
interfaces: value.interfaces,
source_config: value.source_config,
target_config: value.target_config,
}
impl TryFrom<PutLink> for Link {
type Error = Box<dyn Error + Send + Sync>;
fn try_from(value: PutLink) -> Result<Link, Self::Error> {
Link::builder()
.source_id(&value.source_id)
.target(&value.target)
.name(&value.name)
.wit_namespace(&value.wit_namespace)
.wit_package(&value.wit_package)
.interfaces(value.interfaces)
.source_config(value.source_config)
.target_config(value.target_config)
.build()
}
}

306
crates/wadm/src/config.rs Normal file
View File

@ -0,0 +1,306 @@
#[cfg(feature = "http_admin")]
use core::net::SocketAddr;
use std::path::PathBuf;
#[cfg(feature = "cli")]
use clap::Parser;
use wadm_types::api::DEFAULT_WADM_TOPIC_PREFIX;
use crate::nats::StreamPersistence;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "cli", derive(Parser))]
#[cfg_attr(feature = "cli", command(name = clap::crate_name!(), version = clap::crate_version!(), about = "wasmCloud Application Deployment Manager", long_about = None))]
pub struct WadmConfig {
/// The ID for this wadm process. Defaults to a random UUIDv4 if none is provided. This is used
/// to help with debugging when identifying which process is doing the work
#[cfg_attr(
feature = "cli",
arg(short = 'i', long = "host-id", env = "WADM_HOST_ID")
)]
pub host_id: Option<String>,
/// Whether or not to use structured log output (as JSON)
#[cfg_attr(
feature = "cli",
arg(
short = 'l',
long = "structured-logging",
default_value = "false",
env = "WADM_STRUCTURED_LOGGING"
)
)]
pub structured_logging: bool,
/// Whether or not to enable opentelemetry tracing
#[cfg_attr(
feature = "cli",
arg(
short = 't',
long = "tracing",
default_value = "false",
env = "WADM_TRACING_ENABLED"
)
)]
pub tracing_enabled: bool,
/// The endpoint to use for tracing. Setting this flag enables tracing, even if --tracing is set
/// to false. Defaults to http://localhost:4318/v1/traces if not set and tracing is enabled
#[cfg_attr(
feature = "cli",
arg(short = 'e', long = "tracing-endpoint", env = "WADM_TRACING_ENDPOINT")
)]
pub tracing_endpoint: Option<String>,
/// The NATS JetStream domain to connect to
#[cfg_attr(feature = "cli", arg(short = 'd', env = "WADM_JETSTREAM_DOMAIN"))]
pub domain: Option<String>,
/// (Advanced) Tweak the maximum number of jobs to run for handling events and commands. Be
/// careful how you use this as it can affect performance
#[cfg_attr(
feature = "cli",
arg(short = 'j', long = "max-jobs", env = "WADM_MAX_JOBS")
)]
pub max_jobs: Option<usize>,
/// The URL of the nats server you want to connect to
#[cfg_attr(
feature = "cli",
arg(
short = 's',
long = "nats-server",
env = "WADM_NATS_SERVER",
default_value = "127.0.0.1:4222"
)
)]
pub nats_server: String,
/// Use the specified nkey file or seed literal for authentication. Must be used in conjunction with --nats-jwt
#[cfg_attr(
feature = "cli",
arg(
long = "nats-seed",
env = "WADM_NATS_NKEY",
conflicts_with = "nats_creds",
requires = "nats_jwt"
)
)]
pub nats_seed: Option<String>,
/// Use the specified jwt file or literal for authentication. Must be used in conjunction with --nats-nkey
#[cfg_attr(
feature = "cli",
arg(
long = "nats-jwt",
env = "WADM_NATS_JWT",
conflicts_with = "nats_creds",
requires = "nats_seed"
)
)]
pub nats_jwt: Option<String>,
/// (Optional) NATS credential file to use when authenticating
#[cfg_attr(
feature = "cli", arg(
long = "nats-creds-file",
env = "WADM_NATS_CREDS_FILE",
conflicts_with_all = ["nats_seed", "nats_jwt"],
))]
pub nats_creds: Option<PathBuf>,
/// (Optional) NATS TLS certificate file to use when authenticating
#[cfg_attr(
feature = "cli",
arg(long = "nats-tls-ca-file", env = "WADM_NATS_TLS_CA_FILE")
)]
pub nats_tls_ca_file: Option<PathBuf>,
/// Name of the bucket used for storage of lattice state
#[cfg_attr(
feature = "cli",
arg(
long = "state-bucket-name",
env = "WADM_STATE_BUCKET_NAME",
default_value = "wadm_state"
)
)]
pub state_bucket: String,
/// The amount of time in seconds to give for hosts to fail to heartbeat and be removed from the
/// store. By default, this is 70s because it is 2x the host heartbeat interval plus a little padding
#[cfg_attr(
feature = "cli",
arg(
long = "cleanup-interval",
env = "WADM_CLEANUP_INTERVAL",
default_value = "70"
)
)]
pub cleanup_interval: u64,
/// The API topic prefix to use. This is an advanced setting that should only be used if you
/// know what you are doing
#[cfg_attr(
feature = "cli", arg(
long = "api-prefix",
env = "WADM_API_PREFIX",
default_value = DEFAULT_WADM_TOPIC_PREFIX
))]
pub api_prefix: String,
/// This prefix to used for the internal streams. When running in a multitenant environment,
/// clients share the same JS domain (since messages need to come from lattices).
/// Setting a stream prefix makes it possible to have a separate stream for different wadms running in a multitenant environment.
/// This is an advanced setting that should only be used if you know what you are doing.
#[cfg_attr(
feature = "cli",
arg(long = "stream-prefix", env = "WADM_STREAM_PREFIX")
)]
pub stream_prefix: Option<String>,
/// Name of the bucket used for storage of manifests
#[cfg_attr(
feature = "cli",
arg(
long = "manifest-bucket-name",
env = "WADM_MANIFEST_BUCKET_NAME",
default_value = "wadm_manifests"
)
)]
pub manifest_bucket: String,
/// Run wadm in multitenant mode. This is for advanced multitenant use cases with segmented NATS
/// account traffic and not simple cases where all lattices use credentials from the same
/// account. See the deployment guide for more information
#[cfg_attr(
feature = "cli",
arg(long = "multitenant", env = "WADM_MULTITENANT", hide = true)
)]
pub multitenant: bool,
//
// Max bytes configuration for streams. Primarily configurable to enable deployment on NATS infra
// with limited resources.
//
/// Maximum bytes to keep for the state bucket
#[cfg_attr(
feature = "cli", arg(
long = "state-bucket-max-bytes",
env = "WADM_STATE_BUCKET_MAX_BYTES",
default_value_t = -1,
hide = true
))]
pub max_state_bucket_bytes: i64,
/// Maximum bytes to keep for the manifest bucket
#[cfg_attr(
feature = "cli", arg(
long = "manifest-bucket-max-bytes",
env = "WADM_MANIFEST_BUCKET_MAX_BYTES",
default_value_t = -1,
hide = true
))]
pub max_manifest_bucket_bytes: i64,
/// Nats streams storage type
#[cfg_attr(
feature = "cli", arg(
long = "stream-persistence",
env = "WADM_STREAM_PERSISTENCE",
default_value_t = StreamPersistence::File
))]
pub stream_persistence: StreamPersistence,
/// Maximum bytes to keep for the command stream
#[cfg_attr(
feature = "cli", arg(
long = "command-stream-max-bytes",
env = "WADM_COMMAND_STREAM_MAX_BYTES",
default_value_t = -1,
hide = true
))]
pub max_command_stream_bytes: i64,
/// Maximum bytes to keep for the event stream
#[cfg_attr(
feature = "cli", arg(
long = "event-stream-max-bytes",
env = "WADM_EVENT_STREAM_MAX_BYTES",
default_value_t = -1,
hide = true
))]
pub max_event_stream_bytes: i64,
/// Maximum bytes to keep for the event consumer stream
#[cfg_attr(
feature = "cli", arg(
long = "event-consumer-stream-max-bytes",
env = "WADM_EVENT_CONSUMER_STREAM_MAX_BYTES",
default_value_t = -1,
hide = true
))]
pub max_event_consumer_stream_bytes: i64,
/// Maximum bytes to keep for the status stream
#[cfg_attr(
feature = "cli", arg(
long = "status-stream-max-bytes",
env = "WADM_STATUS_STREAM_MAX_BYTES",
default_value_t = -1,
hide = true
))]
pub max_status_stream_bytes: i64,
/// Maximum bytes to keep for the notify stream
#[cfg_attr(
feature = "cli", arg(
long = "notify-stream-max-bytes",
env = "WADM_NOTIFY_STREAM_MAX_BYTES",
default_value_t = -1,
hide = true
))]
pub max_notify_stream_bytes: i64,
/// Maximum bytes to keep for the wasmbus event stream
#[cfg_attr(
feature = "cli", arg(
long = "wasmbus-event-stream-max-bytes",
env = "WADM_WASMBUS_EVENT_STREAM_MAX_BYTES",
default_value_t = -1,
hide = true
))]
pub max_wasmbus_event_stream_bytes: i64,
#[cfg(feature = "http_admin")]
#[cfg_attr(feature = "cli", clap(long = "http-admin", env = "WADM_HTTP_ADMIN"))]
/// HTTP administration endpoint address
pub http_admin: Option<SocketAddr>,
}
impl Default for WadmConfig {
fn default() -> Self {
Self {
host_id: None,
domain: None,
max_jobs: None,
nats_server: "127.0.0.1:4222".to_string(),
nats_seed: None,
nats_jwt: None,
nats_creds: None,
nats_tls_ca_file: None,
state_bucket: "wadm_state".to_string(),
cleanup_interval: 70,
api_prefix: DEFAULT_WADM_TOPIC_PREFIX.to_string(),
stream_prefix: None,
manifest_bucket: "wadm_manifests".to_string(),
multitenant: false,
max_state_bucket_bytes: -1,
max_manifest_bucket_bytes: -1,
stream_persistence: StreamPersistence::File,
max_command_stream_bytes: -1,
max_event_stream_bytes: -1,
max_event_consumer_stream_bytes: -1,
max_status_stream_bytes: -1,
max_notify_stream_bytes: -1,
max_wasmbus_event_stream_bytes: -1,
structured_logging: false,
tracing_enabled: false,
tracing_endpoint: None,
#[cfg(feature = "http_admin")]
http_admin: None,
}
}
}

View File

@ -1,5 +1,6 @@
//! A module for creating and consuming a stream of commands from NATS
use std::collections::HashMap;
use std::pin::Pin;
use std::task::{Context, Poll};
@ -13,7 +14,7 @@ use async_nats::{
use futures::{Stream, TryStreamExt};
use tracing::{error, warn};
use super::{CreateConsumer, ScopedMessage};
use super::{CreateConsumer, ScopedMessage, LATTICE_METADATA_KEY, MULTITENANT_METADATA_KEY};
use crate::commands::*;
/// The name of the durable NATS stream and consumer that contains incoming lattice events
@ -42,10 +43,19 @@ impl CommandConsumer {
return Err(format!("Topic {topic} does not match for lattice ID {lattice_id}").into());
}
let consumer_name = if let Some(prefix) = multitenant_prefix {
format!("{COMMANDS_CONSUMER_PREFIX}-{lattice_id}_{prefix}")
let (consumer_name, metadata) = if let Some(prefix) = multitenant_prefix {
(
format!("{COMMANDS_CONSUMER_PREFIX}-{lattice_id}_{prefix}"),
HashMap::from([
(LATTICE_METADATA_KEY.to_string(), lattice_id.to_string()),
(MULTITENANT_METADATA_KEY.to_string(), prefix.to_string()),
]),
)
} else {
format!("{COMMANDS_CONSUMER_PREFIX}-{lattice_id}")
(
format!("{COMMANDS_CONSUMER_PREFIX}-{lattice_id}"),
HashMap::from([(LATTICE_METADATA_KEY.to_string(), lattice_id.to_string())]),
)
};
let consumer = stream
.get_or_create_consumer(
@ -61,6 +71,7 @@ impl CommandConsumer {
max_deliver: 3,
deliver_policy: async_nats::jetstream::consumer::DeliverPolicy::All,
filter_subject: topic.to_owned(),
metadata,
..Default::default()
},
)

View File

@ -1,5 +1,6 @@
//! A module for creating and consuming a stream of events from a wasmcloud lattice
use std::collections::HashMap;
use std::convert::TryFrom;
use std::pin::Pin;
use std::task::{Context, Poll};
@ -14,7 +15,7 @@ use async_nats::{
use futures::{Stream, TryStreamExt};
use tracing::{debug, error, warn};
use super::{CreateConsumer, ScopedMessage};
use super::{CreateConsumer, ScopedMessage, LATTICE_METADATA_KEY, MULTITENANT_METADATA_KEY};
use crate::events::*;
/// The name of the durable NATS stream and consumer that contains incoming lattice events
@ -42,10 +43,19 @@ impl EventConsumer {
if !topic.contains(lattice_id) {
return Err(format!("Topic {topic} does not match for lattice ID {lattice_id}").into());
}
let consumer_name = if let Some(prefix) = multitenant_prefix {
format!("{EVENTS_CONSUMER_PREFIX}-{lattice_id}_{prefix}")
let (consumer_name, metadata) = if let Some(prefix) = multitenant_prefix {
(
format!("{EVENTS_CONSUMER_PREFIX}-{lattice_id}_{prefix}"),
HashMap::from([
(LATTICE_METADATA_KEY.to_string(), lattice_id.to_string()),
(MULTITENANT_METADATA_KEY.to_string(), prefix.to_string()),
]),
)
} else {
format!("{EVENTS_CONSUMER_PREFIX}-{lattice_id}")
(
format!("{EVENTS_CONSUMER_PREFIX}-{lattice_id}"),
HashMap::from([(LATTICE_METADATA_KEY.to_string(), lattice_id.to_string())]),
)
};
let consumer = stream
.get_or_create_consumer(
@ -61,6 +71,7 @@ impl EventConsumer {
max_deliver: 3,
deliver_policy: async_nats::jetstream::consumer::DeliverPolicy::All,
filter_subject: topic.to_owned(),
metadata,
..Default::default()
},
)

View File

@ -9,6 +9,8 @@ use tokio::{
};
use tracing::{error, instrument, trace, warn, Instrument};
use crate::consumers::{LATTICE_METADATA_KEY, MULTITENANT_METADATA_KEY};
use super::{CreateConsumer, ScopedMessage};
/// A convenience type for returning work results
@ -141,15 +143,24 @@ impl<C> ConsumerManager<C> {
}
};
// TODO: This is somewhat brittle as we could change naming schemes, but it is
// good enough for now. We are just taking the name (which should be of the
// format `<consumer_prefix>-<lattice_prefix>_<multitenant_prefix>`), but this makes sure
// we are always getting the last thing in case of other underscores
//
// When NATS 2.10 is out, store this as metadata on the stream.
let (lattice_id, multitenant_prefix) = match extract_lattice_and_multitenant(&info.name) {
(Some(id), prefix) => (id, prefix),
(None, _) => return None,
// Now that wadm is using NATS 2.10, the lattice and multitenant prefix are stored in the consumer metadata
// as a fallback for older versions, we can still extract it from the consumer name in the
// form `<consumer_prefix>-<lattice_prefix>_<multitenant_prefix>`
let (lattice_id, multitenant_prefix) = match (info.config.metadata.get(LATTICE_METADATA_KEY), info.config.metadata.get(MULTITENANT_METADATA_KEY)) {
(Some(lattice), Some(multitenant_prefix)) => {
trace!(%lattice, %multitenant_prefix, "Found lattice and multitenant prefix in consumer metadata");
(lattice.to_owned(), Some(multitenant_prefix.to_owned()))
}
(Some(lattice), None) => {
trace!(%lattice, "Found lattice in consumer metadata");
(lattice.to_owned(), None)
}
_ => {
match extract_lattice_and_multitenant(&info.name) {
(Some(id), prefix) => (id, prefix),
(None, _) => return None,
}
}
};
// Don't create multitenant consumers if running in single tenant mode, and vice versa

View File

@ -16,6 +16,9 @@ pub mod manager;
/// The default time given for a command to ack. This is longer than events due to the possible need for more processing time
pub const DEFAULT_ACK_TIME: Duration = Duration::from_secs(2);
pub const LATTICE_METADATA_KEY: &str = "lattice";
pub const MULTITENANT_METADATA_KEY: &str = "multitenant_prefix";
pub use commands::*;
pub use events::*;

View File

@ -11,9 +11,7 @@ use std::{
use cloudevents::{AttributesReader, Data, Event as CloudEvent, EventBuilder, EventBuilderV10};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use wasmcloud_control_interface::{
ComponentDescription, InterfaceLinkDefinition, ProviderDescription,
};
use wasmcloud_control_interface::{ComponentDescription, Link, ProviderDescription};
use wadm_types::Manifest;
@ -302,7 +300,6 @@ pub struct ComponentScaled {
pub claims: Option<ComponentClaims>,
pub image_ref: String,
pub max_instances: usize,
// TODO: Once we update to the 1.0 release candidate, this will be component_id
pub component_id: String,
#[serde(default)]
pub host_id: String,
@ -321,7 +318,6 @@ pub struct ComponentScaleFailed {
pub claims: Option<ComponentClaims>,
pub image_ref: String,
pub max_instances: usize,
// TODO: Once we update to the 1.0 release candidate, this will be component_id
pub component_id: String,
#[serde(default)]
pub host_id: String,
@ -426,7 +422,7 @@ event_impl!(
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct LinkdefSet {
#[serde(flatten)]
pub linkdef: InterfaceLinkDefinition,
pub linkdef: Link,
}
event_impl!(LinkdefSet, "com.wasmcloud.lattice.linkdef_set");
@ -463,7 +459,6 @@ event_impl!(ConfigDeleted, "com.wasmcloud.lattice.config_deleted");
pub struct HostStarted {
pub labels: HashMap<String, String>,
pub friendly_name: String,
// TODO: Parse as nkey?
#[serde(default)]
pub id: String,
}
@ -478,7 +473,6 @@ event_impl!(
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct HostStopped {
pub labels: HashMap<String, String>,
// TODO: Parse as nkey?
#[serde(default)]
pub id: String,
}
@ -571,7 +565,8 @@ mod test {
#[test]
fn test_all_supported_events() {
let raw = std::fs::read("../../test/data/events.json").expect("Unable to load test data");
let raw = std::fs::read("../../tests/fixtures/manifests/events.json")
.expect("Unable to load test data");
let all_events: Vec<cloudevents::Event> = serde_json::from_slice(&raw).unwrap();

View File

@ -1,6 +1,38 @@
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use async_nats::jetstream::{stream::Stream, Context};
use config::WadmConfig;
use tokio::{sync::Semaphore, task::JoinSet};
use tracing::log::debug;
#[cfg(feature = "http_admin")]
use anyhow::Context as _;
#[cfg(feature = "http_admin")]
use hyper::body::Bytes;
#[cfg(feature = "http_admin")]
use hyper_util::rt::{TokioExecutor, TokioIo};
#[cfg(feature = "http_admin")]
use tokio::net::TcpListener;
use crate::{
connections::ControlClientConstructor,
consumers::{
manager::{ConsumerManager, WorkerCreator},
*,
},
nats_utils::LatticeIdParser,
scaler::manager::{ScalerManager, WADM_NOTIFY_PREFIX},
server::{ManifestNotifier, Server},
storage::{nats_kv::NatsKvStore, reaper::Reaper},
workers::{CommandPublisher, CommandWorker, EventWorker, StatusPublisher},
};
pub use nats::StreamPersistence;
pub mod commands;
pub mod config;
pub mod consumers;
pub mod events;
pub mod nats_utils;
@ -10,7 +42,10 @@ pub mod server;
pub mod storage;
pub mod workers;
mod connections;
pub(crate) mod model;
mod nats;
mod observer;
#[cfg(test)]
pub mod test_util;
@ -39,3 +74,406 @@ pub const APP_SPEC_ANNOTATION: &str = "wasmcloud.dev/appspec";
pub const SCALER_KEY: &str = "wasmcloud.dev/scaler";
/// The default link name. In the future, this will likely be pulled in from another crate
pub const DEFAULT_LINK_NAME: &str = "default";
/// Default stream name for wadm events
pub const DEFAULT_WADM_EVENT_STREAM_NAME: &str = "wadm_events";
/// Default stream name for wadm event consumer
pub const DEFAULT_WADM_EVENT_CONSUMER_STREAM_NAME: &str = "wadm_event_consumer";
/// Default stream name for wadm commands
pub const DEFAULT_COMMAND_STREAM_NAME: &str = "wadm_commands";
/// Default stream name for wadm status
pub const DEFAULT_STATUS_STREAM_NAME: &str = "wadm_status";
/// Default stream name for wadm notifications
pub const DEFAULT_NOTIFY_STREAM_NAME: &str = "wadm_notify";
/// Default stream name for wasmbus events
pub const DEFAULT_WASMBUS_EVENT_STREAM_NAME: &str = "wasmbus_events";
/// Start wadm with the provided [WadmConfig], returning [JoinSet] with two tasks:
/// 1. The server task that listens for API requests
/// 2. The observer task that listens for events and commands
///
/// When embedding wadm in another application, this function should be called to start the wadm
/// server and observer tasks.
///
/// # Usage
///
/// ```no_run
/// async {
/// let config = wadm::config::WadmConfig::default();
/// let mut wadm = wadm::start_wadm(config).await.expect("should start wadm");
/// tokio::select! {
/// res = wadm.join_next() => {
/// match res {
/// Some(Ok(_)) => {
/// tracing::info!("WADM has exited successfully");
/// std::process::exit(0);
/// }
/// Some(Err(e)) => {
/// tracing::error!("WADM has exited with an error: {:?}", e);
/// std::process::exit(1);
/// }
/// None => {
/// tracing::info!("WADM server did not start");
/// std::process::exit(0);
/// }
/// }
/// }
/// _ = tokio::signal::ctrl_c() => {
/// tracing::info!("Received Ctrl+C, shutting down");
/// std::process::exit(0);
/// }
/// }
/// };
/// ```
pub async fn start_wadm(config: WadmConfig) -> Result<JoinSet<Result<()>>> {
// Build storage adapter for lattice state (on by default)
let (client, context) = nats::get_client_and_context(
config.nats_server.clone(),
config.domain.clone(),
config.nats_seed.clone(),
config.nats_jwt.clone(),
config.nats_creds.clone(),
config.nats_tls_ca_file.clone(),
)
.await?;
// TODO: We will probably need to set up all the flags (like lattice prefix and topic prefix) down the line
let connection_pool = ControlClientConstructor::new(client.clone(), None);
let trimmer: &[_] = &['.', '>', '*'];
let store = nats::ensure_kv_bucket(
&context,
config.state_bucket,
1,
config.max_state_bucket_bytes,
config.stream_persistence.into(),
)
.await?;
let state_storage = NatsKvStore::new(store);
let manifest_storage = nats::ensure_kv_bucket(
&context,
config.manifest_bucket,
1,
config.max_manifest_bucket_bytes,
config.stream_persistence.into(),
)
.await?;
let internal_stream_name = |stream_name: &str| -> String {
match config.stream_prefix.clone() {
Some(stream_prefix) => {
format!(
"{}.{}",
stream_prefix.trim_end_matches(trimmer),
stream_name
)
}
None => stream_name.to_string(),
}
};
debug!("Ensuring wadm event stream");
let event_stream = nats::ensure_limits_stream(
&context,
internal_stream_name(DEFAULT_WADM_EVENT_STREAM_NAME),
vec![DEFAULT_WADM_EVENTS_TOPIC.to_owned()],
Some(
"A stream that stores all events coming in on the wadm.evt subject in a cluster"
.to_string(),
),
config.max_event_stream_bytes,
config.stream_persistence.into(),
)
.await?;
debug!("Ensuring command stream");
let command_stream = nats::ensure_stream(
&context,
internal_stream_name(DEFAULT_COMMAND_STREAM_NAME),
vec![DEFAULT_COMMANDS_TOPIC.to_owned()],
Some("A stream that stores all commands for wadm".to_string()),
config.max_command_stream_bytes,
config.stream_persistence.into(),
)
.await?;
let status_stream = nats::ensure_status_stream(
&context,
internal_stream_name(DEFAULT_STATUS_STREAM_NAME),
vec![DEFAULT_STATUS_TOPIC.to_owned()],
config.max_status_stream_bytes,
config.stream_persistence.into(),
)
.await?;
debug!("Ensuring wasmbus event stream");
// Remove the previous wadm_(multitenant)_mirror streams so that they don't
// prevent us from creating the new wasmbus_(multitenant)_events stream
// TODO(joonas): Remove this some time in the future once we're confident
// enough that there are no more wadm_(multitenant)_mirror streams around.
for mirror_stream_name in &["wadm_mirror", "wadm_multitenant_mirror"] {
if (context.get_stream(mirror_stream_name).await).is_ok() {
context.delete_stream(mirror_stream_name).await?;
}
}
let wasmbus_event_subjects = match config.multitenant {
true => vec![DEFAULT_MULTITENANT_EVENTS_TOPIC.to_owned()],
false => vec![DEFAULT_EVENTS_TOPIC.to_owned()],
};
let wasmbus_event_stream = nats::ensure_limits_stream(
&context,
DEFAULT_WASMBUS_EVENT_STREAM_NAME.to_string(),
wasmbus_event_subjects.clone(),
Some(
"A stream that stores all events coming in on the wasmbus.evt subject in a cluster"
.to_string(),
),
config.max_wasmbus_event_stream_bytes,
config.stream_persistence.into(),
)
.await?;
debug!("Ensuring notify stream");
let notify_stream = nats::ensure_notify_stream(
&context,
DEFAULT_NOTIFY_STREAM_NAME.to_owned(),
vec![format!("{WADM_NOTIFY_PREFIX}.*")],
config.max_notify_stream_bytes,
config.stream_persistence.into(),
)
.await?;
debug!("Ensuring event consumer stream");
let event_consumer_stream = nats::ensure_event_consumer_stream(
&context,
DEFAULT_WADM_EVENT_CONSUMER_STREAM_NAME.to_owned(),
DEFAULT_WADM_EVENT_CONSUMER_TOPIC.to_owned(),
vec![&wasmbus_event_stream, &event_stream],
Some(
"A stream that sources from wadm_events and wasmbus_events for wadm event consumer's use"
.to_string(),
),
config.max_event_consumer_stream_bytes,
config.stream_persistence.into(),
)
.await?;
debug!("Creating event consumer manager");
let permit_pool = Arc::new(Semaphore::new(
config.max_jobs.unwrap_or(Semaphore::MAX_PERMITS),
));
let event_worker_creator = EventWorkerCreator {
state_store: state_storage.clone(),
manifest_store: manifest_storage.clone(),
pool: connection_pool.clone(),
command_topic_prefix: DEFAULT_COMMANDS_TOPIC.trim_matches(trimmer).to_owned(),
publisher: context.clone(),
notify_stream,
status_stream: status_stream.clone(),
};
let events_manager: ConsumerManager<EventConsumer> = ConsumerManager::new(
permit_pool.clone(),
event_consumer_stream,
event_worker_creator.clone(),
config.multitenant,
)
.await;
debug!("Creating command consumer manager");
let command_worker_creator = CommandWorkerCreator {
pool: connection_pool,
};
let commands_manager: ConsumerManager<CommandConsumer> = ConsumerManager::new(
permit_pool.clone(),
command_stream,
command_worker_creator.clone(),
config.multitenant,
)
.await;
// TODO(thomastaylor312): We might want to figure out how not to run this globally. Doing a
// synthetic event sent to the stream could be nice, but all the wadm processes would still fire
// off that tick, resulting in multiple people handling. We could maybe get it to work with the
// right duplicate window, but we have no idea when each process could fire a tick. Worst case
// scenario right now is that multiple fire simultaneously and a few of them just delete nothing
let reaper = Reaper::new(
state_storage.clone(),
Duration::from_secs(config.cleanup_interval / 2),
[],
);
let wadm_event_prefix = DEFAULT_WADM_EVENTS_TOPIC.trim_matches(trimmer);
debug!("Creating lattice observer");
let observer = observer::Observer {
parser: LatticeIdParser::new("wasmbus", config.multitenant),
command_manager: commands_manager,
event_manager: events_manager,
reaper,
client: client.clone(),
command_worker_creator,
event_worker_creator,
};
debug!("Subscribing to API topic");
let server = Server::new(
manifest_storage,
client,
Some(&config.api_prefix),
config.multitenant,
status_stream,
ManifestNotifier::new(wadm_event_prefix, context),
)
.await?;
let mut tasks = JoinSet::new();
#[cfg(feature = "http_admin")]
if let Some(addr) = config.http_admin {
debug!("Setting up HTTP administration endpoint");
let socket = TcpListener::bind(addr)
.await
.context("failed to bind on HTTP administation endpoint")?;
let svc = hyper::service::service_fn(move |req| {
const OK: &str = r#"{"status":"ok"}"#;
async move {
let (http::request::Parts { method, uri, .. }, _) = req.into_parts();
match (method.as_str(), uri.path()) {
("HEAD", "/livez") => Ok(http::Response::default()),
("GET", "/livez") => Ok(http::Response::new(http_body_util::Full::new(
Bytes::from(OK),
))),
(method, "/livez") => http::Response::builder()
.status(http::StatusCode::METHOD_NOT_ALLOWED)
.body(http_body_util::Full::new(Bytes::from(format!(
"method `{method}` not supported for path `/livez`"
)))),
("HEAD", "/readyz") => Ok(http::Response::default()),
("GET", "/readyz") => Ok(http::Response::new(http_body_util::Full::new(
Bytes::from(OK),
))),
(method, "/readyz") => http::Response::builder()
.status(http::StatusCode::METHOD_NOT_ALLOWED)
.body(http_body_util::Full::new(Bytes::from(format!(
"method `{method}` not supported for path `/readyz`"
)))),
(.., path) => http::Response::builder()
.status(http::StatusCode::NOT_FOUND)
.body(http_body_util::Full::new(Bytes::from(format!(
"unknown endpoint `{path}`"
)))),
}
}
});
let srv = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
tasks.spawn(async move {
loop {
let stream = match socket.accept().await {
Ok((stream, _)) => stream,
Err(err) => {
tracing::error!(?err, "failed to accept HTTP administration connection");
continue;
}
};
if let Err(err) = srv.serve_connection(TokioIo::new(stream), svc).await {
tracing::error!(?err, "failed to serve HTTP administration connection");
}
}
});
}
// Subscribe and handle API requests
tasks.spawn(server.serve());
// Observe and handle events
tasks.spawn(observer.observe(wasmbus_event_subjects));
Ok(tasks)
}
#[derive(Clone)]
struct CommandWorkerCreator {
pool: ControlClientConstructor,
}
#[async_trait::async_trait]
impl WorkerCreator for CommandWorkerCreator {
type Output = CommandWorker;
async fn create(
&self,
lattice_id: &str,
multitenant_prefix: Option<&str>,
) -> anyhow::Result<Self::Output> {
let client = self.pool.get_connection(lattice_id, multitenant_prefix);
Ok(CommandWorker::new(client))
}
}
#[derive(Clone)]
struct EventWorkerCreator<StateStore> {
state_store: StateStore,
manifest_store: async_nats::jetstream::kv::Store,
pool: ControlClientConstructor,
command_topic_prefix: String,
publisher: Context,
notify_stream: Stream,
status_stream: Stream,
}
#[async_trait::async_trait]
impl<StateStore> WorkerCreator for EventWorkerCreator<StateStore>
where
StateStore: crate::storage::Store + Send + Sync + Clone + 'static,
{
type Output = EventWorker<StateStore, wasmcloud_control_interface::Client, Context>;
async fn create(
&self,
lattice_id: &str,
multitenant_prefix: Option<&str>,
) -> anyhow::Result<Self::Output> {
let client = self.pool.get_connection(lattice_id, multitenant_prefix);
let command_publisher = CommandPublisher::new(
self.publisher.clone(),
&format!("{}.{lattice_id}", self.command_topic_prefix),
);
let status_publisher = StatusPublisher::new(
self.publisher.clone(),
Some(self.status_stream.clone()),
&format!("wadm.status.{lattice_id}"),
);
let manager = ScalerManager::new(
self.publisher.clone(),
self.notify_stream.clone(),
lattice_id,
multitenant_prefix,
self.state_store.clone(),
self.manifest_store.clone(),
command_publisher.clone(),
status_publisher.clone(),
client.clone(),
)
.await?;
Ok(EventWorker::new(
self.state_store.clone(),
client,
command_publisher,
status_publisher,
manager,
))
}
}

View File

@ -159,8 +159,8 @@ mod test {
#[test]
fn test_versioning() {
let mut manifest =
deserialize_yaml("../../oam/simple2.yaml").expect("Should be able to parse");
let mut manifest = deserialize_yaml("../../tests/fixtures/manifests/simple2.yaml")
.expect("Should be able to parse");
let mut stored = StoredManifest::default();
assert!(

View File

@ -5,17 +5,52 @@ use async_nats::{
jetstream::{
self,
kv::{Config as KvConfig, Store},
stream::{Config as StreamConfig, Source, Stream, SubjectTransform},
stream::{Config as StreamConfig, Source, StorageType, Stream, SubjectTransform},
Context,
},
Client, ConnectOptions,
};
use tracing::warn;
use wadm::DEFAULT_EXPIRY_TIME;
use crate::DEFAULT_EXPIRY_TIME;
use tracing::{debug, warn};
#[derive(Debug, Clone, Copy, Default)]
pub enum StreamPersistence {
#[default]
File,
Memory,
}
impl std::fmt::Display for StreamPersistence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StreamPersistence::File => write!(f, "file"),
StreamPersistence::Memory => write!(f, "memory"),
}
}
}
impl From<StreamPersistence> for StorageType {
fn from(persistance: StreamPersistence) -> Self {
match persistance {
StreamPersistence::File => StorageType::File,
StreamPersistence::Memory => StorageType::Memory,
}
}
}
impl From<&str> for StreamPersistence {
fn from(persistance: &str) -> Self {
match persistance {
"file" => StreamPersistence::File,
"memory" => StreamPersistence::Memory,
_ => StreamPersistence::File,
}
}
}
/// Creates a NATS client from the given options
pub async fn get_client_and_context(
pub(crate) async fn get_client_and_context(
url: String,
js_domain: Option<String>,
seed: Option<String>,
@ -120,7 +155,10 @@ pub async fn ensure_stream(
name: String,
subjects: Vec<String>,
description: Option<String>,
max_bytes: i64,
storage: StorageType,
) -> Result<Stream> {
debug!("Ensuring stream {name} exists");
let stream_config = StreamConfig {
name: name.clone(),
description,
@ -128,8 +166,9 @@ pub async fn ensure_stream(
retention: async_nats::jetstream::stream::RetentionPolicy::WorkQueue,
subjects,
max_age: DEFAULT_EXPIRY_TIME,
storage: async_nats::jetstream::stream::StorageType::File,
allow_rollup: false,
max_bytes,
storage,
..Default::default()
};
@ -157,7 +196,10 @@ pub async fn ensure_limits_stream(
name: String,
subjects: Vec<String>,
description: Option<String>,
max_bytes: i64,
storage: StorageType,
) -> Result<Stream> {
debug!("Ensuring stream {name} exists");
let stream_config = StreamConfig {
name: name.clone(),
description,
@ -165,8 +207,9 @@ pub async fn ensure_limits_stream(
retention: async_nats::jetstream::stream::RetentionPolicy::Limits,
subjects,
max_age: DEFAULT_EXPIRY_TIME,
storage: async_nats::jetstream::stream::StorageType::File,
allow_rollup: false,
max_bytes,
storage,
..Default::default()
};
@ -195,7 +238,10 @@ pub async fn ensure_event_consumer_stream(
subject: String,
streams: Vec<&Stream>,
description: Option<String>,
max_bytes: i64,
storage: StorageType,
) -> Result<Stream> {
debug!("Ensuring stream {name} exists");
// This maps the upstream (wasmbus.evt.*.> & wadm.evt.*.>) Streams into
// a set of configuration for the downstream wadm event consumer Stream
// that consolidates them into a single set of subjects (wadm_event_consumer.evt.*.>)
@ -234,8 +280,9 @@ pub async fn ensure_event_consumer_stream(
subjects: vec![],
max_age: DEFAULT_EXPIRY_TIME,
sources: Some(sources),
storage: async_nats::jetstream::stream::StorageType::File,
allow_rollup: false,
max_bytes,
storage,
..Default::default()
};
@ -258,7 +305,10 @@ pub async fn ensure_status_stream(
context: &Context,
name: String,
subjects: Vec<String>,
max_bytes: i64,
storage: StorageType,
) -> Result<Stream> {
debug!("Ensuring stream {name} exists");
context
.get_or_create_stream(StreamConfig {
name,
@ -271,7 +321,8 @@ pub async fn ensure_status_stream(
max_messages_per_subject: 10,
subjects,
max_age: std::time::Duration::from_nanos(0),
storage: async_nats::jetstream::stream::StorageType::File,
max_bytes,
storage,
..Default::default()
})
.await
@ -283,7 +334,10 @@ pub async fn ensure_notify_stream(
context: &Context,
name: String,
subjects: Vec<String>,
max_bytes: i64,
storage: StorageType,
) -> Result<Stream> {
debug!("Ensuring stream {name} exists");
context
.get_or_create_stream(StreamConfig {
name,
@ -292,7 +346,8 @@ pub async fn ensure_notify_stream(
retention: async_nats::jetstream::stream::RetentionPolicy::Interest,
subjects,
max_age: DEFAULT_EXPIRY_TIME,
storage: async_nats::jetstream::stream::StorageType::File,
max_bytes,
storage,
..Default::default()
})
.await
@ -305,7 +360,10 @@ pub async fn ensure_kv_bucket(
context: &Context,
name: String,
history_to_keep: i64,
max_bytes: i64,
storage: StorageType,
) -> Result<Store> {
debug!("Ensuring kv bucket {name} exists");
if let Ok(kv) = context.get_key_value(&name).await {
Ok(kv)
} else {
@ -314,7 +372,8 @@ pub async fn ensure_kv_bucket(
bucket: name,
history: history_to_keep,
num_replicas: 1,
storage: jetstream::stream::StorageType::File,
storage,
max_bytes,
..Default::default()
})
.await
@ -330,7 +389,7 @@ mod test {
#[tokio::test]
async fn can_resolve_jwt_value_and_file() -> Result<()> {
let my_jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2aWRlb0lkIjoiUWpVaUxYSnVjMjl0IiwiaWF0IjoxNjIwNjAzNDY5fQ.2PKx6y2ym6IWbeM6zFgHOkDnZEtGTR3YgYlQ2_Jki5g";
let jwt_path = "./test/data/nats.jwt";
let jwt_path = "../../tests/fixtures/nats.jwt";
let jwt_inside_file = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdHJpbmciOiAiQWNjb3JkIHRvIGFsbCBrbm93biBsb3dzIG9mIGF2aWF0aW9uLCB0aGVyZSBpcyBubyB3YXkgdGhhdCBhIGJlZSBhYmxlIHRvIGZseSJ9.GyU6pTRhflcOg6KBCU6wZedP8BQzLXbdgYIoU6KzzD8";
assert_eq!(

View File

@ -4,12 +4,12 @@ use async_nats::Subscriber;
use futures::{stream::SelectAll, StreamExt, TryFutureExt};
use tracing::{debug, error, instrument, trace, warn};
use wadm::{
use crate::{
consumers::{
manager::{ConsumerManager, WorkerCreator},
CommandConsumer, EventConsumer,
},
events::{EventType, HostHeartbeat, HostStarted},
events::{EventType, HostHeartbeat, HostStarted, ManifestPublished},
nats_utils::LatticeIdParser,
storage::{nats_kv::NatsKvStore, reaper::Reaper, Store},
DEFAULT_COMMANDS_TOPIC, DEFAULT_WADM_EVENT_CONSUMER_TOPIC,
@ -111,15 +111,17 @@ where
}
}
// This is a stupid hacky function to check that this is a host started or host heartbeat event
// without actually parsing
// This is a stupid hacky function to check that this is a host started, host heartbeat, or
// manifest_published event without actually parsing
fn is_event_we_care_about(data: &[u8]) -> bool {
let string_data = match std::str::from_utf8(data) {
Ok(s) => s,
Err(_) => return false,
};
string_data.contains(HostStarted::TYPE) || string_data.contains(HostHeartbeat::TYPE)
string_data.contains(HostStarted::TYPE)
|| string_data.contains(HostHeartbeat::TYPE)
|| string_data.contains(ManifestPublished::TYPE)
}
async fn get_subscriber(

View File

@ -19,6 +19,8 @@ use crate::events::{ConfigDeleted, ConfigSet};
use crate::workers::ConfigSource;
use crate::{commands::Command, events::Event, scaler::Scaler};
const CONFIG_SCALER_KIND: &str = "ConfigScaler";
pub struct ConfigScaler<ConfigSource> {
config_bucket: ConfigSource,
id: String,
@ -36,6 +38,14 @@ impl<C: ConfigSource + Send + Sync + Clone> Scaler for ConfigScaler<C> {
&self.id
}
fn kind(&self) -> &str {
CONFIG_SCALER_KIND
}
fn name(&self) -> String {
self.config_name.to_string()
}
async fn status(&self) -> StatusInfo {
let _ = self.reconcile().await;
self.status.read().await.to_owned()

View File

@ -0,0 +1,780 @@
//! Contains code for converting the list of [`Component`]s in an application into a list of [`Scaler`]s
//! that are responsible for monitoring and enforcing the desired state of a lattice
use std::{collections::HashMap, time::Duration};
use anyhow::Result;
use tracing::{error, warn};
use wadm_types::{
api::StatusInfo, CapabilityProperties, Component, ComponentProperties, ConfigProperty,
LinkProperty, Policy, Properties, SecretProperty, SharedApplicationComponentProperties,
SpreadScalerProperty, Trait, TraitProperty, DAEMONSCALER_TRAIT, LINK_TRAIT, SPREADSCALER_TRAIT,
};
use wasmcloud_secrets_types::SECRET_PREFIX;
use crate::{
publisher::Publisher,
scaler::{
spreadscaler::{link::LINK_SCALER_KIND, ComponentSpreadScaler, SPREAD_SCALER_KIND},
statusscaler::StatusScaler,
Scaler,
},
storage::{snapshot::SnapshotStore, ReadStore},
workers::{ConfigSource, LinkSource, SecretSource},
DEFAULT_LINK_NAME,
};
use super::{
configscaler::ConfigScaler,
daemonscaler::{provider::ProviderDaemonScaler, ComponentDaemonScaler},
secretscaler::SecretScaler,
spreadscaler::{
link::{LinkScaler, LinkScalerConfig},
provider::{ProviderSpreadConfig, ProviderSpreadScaler},
},
BackoffWrapper,
};
pub(crate) type BoxedScaler = Box<dyn Scaler + Send + Sync + 'static>;
pub(crate) type ScalerList = Vec<BoxedScaler>;
const EMPTY_TRAIT_VEC: Vec<Trait> = Vec::new();
/// Converts a list of manifest [`Component`]s into a [`ScalerList`], resolving shared application
/// references, links, configuration and secrets as necessary.
///
/// # Arguments
/// * `components` - The list of components to convert
/// * `policies` - The policies to use when creating the scalers so they can access secrets
/// * `lattice_id` - The lattice id the scalers operate on
/// * `notifier` - The publisher to use when creating the scalers so they can report status
/// * `name` - The name of the manifest that the scalers are being created for
/// * `notifier_subject` - The subject to use when creating the scalers so they can report status
/// * `snapshot_data` - The store to use when creating the scalers so they can access lattice state
pub(crate) fn manifest_components_to_scalers<S, P, L>(
components: &[Component],
policies: &HashMap<&String, &Policy>,
lattice_id: &str,
manifest_name: &str,
notifier_subject: &str,
notifier: &P,
snapshot_data: &SnapshotStore<S, L>,
) -> ScalerList
where
S: ReadStore + Send + Sync + Clone + 'static,
P: Publisher + Clone + Send + Sync + 'static,
L: LinkSource + ConfigSource + SecretSource + Clone + Send + Sync + 'static,
{
let mut scalers: ScalerList = Vec::new();
components
.iter()
.for_each(|component| match &component.properties {
Properties::Component { properties } => {
// Determine if this component is contained in this manifest or a shared application
let (application_name, component_name) = match resolve_manifest_component(
manifest_name,
&component.name,
properties.image.as_ref(),
properties.application.as_ref(),
) {
Ok(names) => names,
Err(err) => {
error!(err);
scalers.push(Box::new(StatusScaler::new(
uuid::Uuid::new_v4().to_string(),
SPREAD_SCALER_KIND,
&component.name,
StatusInfo::failed(err),
)) as BoxedScaler);
return;
}
};
component_scalers(
&mut scalers,
components,
properties,
component.traits.as_ref(),
manifest_name,
application_name,
component_name,
lattice_id,
policies,
notifier_subject,
notifier,
snapshot_data,
)
}
Properties::Capability { properties } => {
// Determine if this component is contained in this manifest or a shared application
let (application_name, component_name) = match resolve_manifest_component(
manifest_name,
&component.name,
properties.image.as_ref(),
properties.application.as_ref(),
) {
Ok(names) => names,
Err(err) => {
error!(err);
scalers.push(Box::new(StatusScaler::new(
uuid::Uuid::new_v4().to_string(),
SPREAD_SCALER_KIND,
&component.name,
StatusInfo::failed(err),
)) as BoxedScaler);
return;
}
};
provider_scalers(
&mut scalers,
components,
properties,
component.traits.as_ref(),
manifest_name,
application_name,
component_name,
lattice_id,
policies,
notifier_subject,
notifier,
snapshot_data,
)
}
});
scalers
}
/// Helper function, primarily to remove nesting, that extends a [`ScalerList`] with all scalers
/// from a (Wasm) component [`Component`]
///
/// # Arguments
/// * `scalers` - The list of scalers to extend
/// * `components` - The list of components to convert
/// * `properties` - The properties of the component to convert
/// * `traits` - The traits of the component to convert
/// * `manifest_name` - The name of the manifest that the scalers are being created for
/// * `application_name` - The name of the application that the scalers are being created for
/// * `component_name` - The name of the component to convert
/// * **The following arguments are required to create scalers, passed directly through to the scaler
/// * `lattice_id` - The lattice id the scalers operate on
/// * `policies` - The policies to use when creating the scalers so they can access secrets
/// * `notifier_subject` - The subject to use when creating the scalers so they can report status
/// * `notifier` - The publisher to use when creating the scalers so they can report status
/// * `snapshot_data` - The store to use when creating the scalers so they can access lattice state
#[allow(clippy::too_many_arguments)]
fn component_scalers<S, P, L>(
scalers: &mut ScalerList,
components: &[Component],
properties: &ComponentProperties,
traits: Option<&Vec<Trait>>,
manifest_name: &str,
application_name: &str,
component_name: &str,
lattice_id: &str,
policies: &HashMap<&String, &Policy>,
notifier_subject: &str,
notifier: &P,
snapshot_data: &SnapshotStore<S, L>,
) where
S: ReadStore + Send + Sync + Clone + 'static,
P: Publisher + Clone + Send + Sync + 'static,
L: LinkSource + ConfigSource + SecretSource + Clone + Send + Sync + 'static,
{
scalers.extend(traits.unwrap_or(&EMPTY_TRAIT_VEC).iter().filter_map(|trt| {
// If an image is specified, then it's a component in the same manifest. Otherwise, it's a shared component
let component_id = if properties.image.is_some() {
compute_component_id(manifest_name, properties.id.as_ref(), component_name)
} else {
compute_component_id(application_name, properties.id.as_ref(), component_name)
};
let (config_scalers, mut config_names) =
config_to_scalers(snapshot_data, manifest_name, &properties.config);
let (secret_scalers, secret_names) = secrets_to_scalers(
snapshot_data,
manifest_name,
&properties.secrets,
policies,
);
config_names.append(&mut secret_names.clone());
// TODO(#451): Consider a way to report on status of a shared component
match (trt.trait_type.as_str(), &trt.properties, &properties.image) {
// Shared application components already have their own spread/daemon scalers, you
// cannot modify them from another manifest
(SPREADSCALER_TRAIT, TraitProperty::SpreadScaler(_), None) => {
warn!(
"Unsupported SpreadScaler trait specified for a shared component {component_name}"
);
None
}
(DAEMONSCALER_TRAIT, TraitProperty::SpreadScaler(_), None) => {
warn!(
"Unsupported DaemonScaler trait specified for a shared component {component_name}"
);
None
}
(SPREADSCALER_TRAIT, TraitProperty::SpreadScaler(p), Some(image_ref)) => {
// If the image is not specified, then it's a reference to a shared provider
// in a different manifest
Some(Box::new(BackoffWrapper::new(
ComponentSpreadScaler::new(
snapshot_data.clone(),
image_ref.clone(),
component_id,
lattice_id.to_owned(),
application_name.to_owned(),
p.to_owned(),
component_name,
config_names,
),
notifier.clone(),
config_scalers,
secret_scalers,
notifier_subject,
application_name,
Some(Duration::from_secs(5)),
)) as BoxedScaler)
}
(DAEMONSCALER_TRAIT, TraitProperty::SpreadScaler(p), Some(image_ref)) => {
Some(Box::new(BackoffWrapper::new(
ComponentDaemonScaler::new(
snapshot_data.clone(),
image_ref.to_owned(),
component_id,
lattice_id.to_owned(),
application_name.to_owned(),
p.to_owned(),
component_name,
config_names,
),
notifier.clone(),
config_scalers,
secret_scalers,
notifier_subject,
application_name,
Some(Duration::from_secs(5)),
)) as BoxedScaler)
}
(LINK_TRAIT, TraitProperty::Link(p), _) => {
// Find the target component of the link and create a scaler for it
components
.iter()
.find_map(|component| match &component.properties {
Properties::Capability {
properties:
CapabilityProperties {
id,
application,
image,
..
},
}
| Properties::Component {
properties:
ComponentProperties {
id,
application,
image,
..
},
} if component.name == p.target.name => Some(link_scaler(
p,
lattice_id,
manifest_name,
application_name,
&component.name,
component_id.to_string(),
id.as_ref(),
image.as_ref(),
application.as_ref(),
policies,
notifier_subject,
notifier,
snapshot_data,
)),
_ => None,
})
}
_ => None,
}
}));
}
/// Helper function, primarily to remove nesting, that extends a [`ScalerList`] with all scalers
/// from a capability provider [`Component`]
/// /// # Arguments
/// * `scalers` - The list of scalers to extend
/// * `components` - The list of components to convert
/// * `properties` - The properties of the capability provider to convert
/// * `traits` - The traits of the component to convert
/// * `manifest_name` - The name of the manifest that the scalers are being created for
/// * `application_name` - The name of the application that the scalers are being created for
/// * `component_name` - The name of the component to convert
/// * **The following arguments are required to create scalers, passed directly through to the scaler
/// * `lattice_id` - The lattice id the scalers operate on
/// * `policies` - The policies to use when creating the scalers so they can access secrets
/// * `notifier_subject` - The subject to use when creating the scalers so they can report status
/// * `notifier` - The publisher to use when creating the scalers so they can report status
/// * `snapshot_data` - The store to use when creating the scalers so they can access lattice state
#[allow(clippy::too_many_arguments)]
fn provider_scalers<S, P, L>(
scalers: &mut ScalerList,
components: &[Component],
properties: &CapabilityProperties,
traits: Option<&Vec<Trait>>,
manifest_name: &str,
application_name: &str,
component_name: &str,
lattice_id: &str,
policies: &HashMap<&String, &Policy>,
notifier_subject: &str,
notifier: &P,
snapshot_data: &SnapshotStore<S, L>,
) where
S: ReadStore + Send + Sync + Clone + 'static,
P: Publisher + Clone + Send + Sync + 'static,
L: LinkSource + ConfigSource + SecretSource + Clone + Send + Sync + 'static,
{
// If an image is specified, then it's a provider in the same manifest. Otherwise, it's a shared component
let provider_id = if properties.image.is_some() {
compute_component_id(manifest_name, properties.id.as_ref(), component_name)
} else {
compute_component_id(application_name, properties.id.as_ref(), component_name)
};
let mut scaler_specified = false;
scalers.extend(traits.unwrap_or(&EMPTY_TRAIT_VEC).iter().filter_map(|trt| {
match (trt.trait_type.as_str(), &trt.properties, &properties.image) {
// Shared application components already have their own spread/daemon scalers, you
// cannot modify them from another manifest
(SPREADSCALER_TRAIT, TraitProperty::SpreadScaler(_), None) => {
warn!(
"Unsupported SpreadScaler trait specified for a shared provider {component_name}"
);
None
}
(DAEMONSCALER_TRAIT, TraitProperty::SpreadScaler(_), None) => {
warn!(
"Unsupported DaemonScaler trait specified for a shared provider {component_name}"
);
None
}
(SPREADSCALER_TRAIT, TraitProperty::SpreadScaler(p), Some(image)) => {
scaler_specified = true;
let (config_scalers, mut config_names) =
config_to_scalers(snapshot_data, application_name, &properties.config);
let (secret_scalers, secret_names) = secrets_to_scalers(
snapshot_data,
application_name,
&properties.secrets,
policies,
);
config_names.append(&mut secret_names.clone());
Some(Box::new(BackoffWrapper::new(
ProviderSpreadScaler::new(
snapshot_data.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_owned(),
provider_id: provider_id.to_owned(),
provider_reference: image.to_owned(),
spread_config: p.to_owned(),
model_name: application_name.to_owned(),
provider_config: config_names,
},
component_name,
),
notifier.clone(),
config_scalers,
secret_scalers,
notifier_subject,
application_name,
// Providers are a bit longer because it can take a bit to download
Some(Duration::from_secs(60)),
)) as BoxedScaler)
}
(DAEMONSCALER_TRAIT, TraitProperty::SpreadScaler(p), Some(image)) => {
scaler_specified = true;
let (config_scalers, mut config_names) =
config_to_scalers(snapshot_data, application_name, &properties.config);
let (secret_scalers, secret_names) = secrets_to_scalers(
snapshot_data,
application_name,
&properties.secrets,
policies,
);
config_names.append(&mut secret_names.clone());
Some(Box::new(BackoffWrapper::new(
ProviderDaemonScaler::new(
snapshot_data.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_owned(),
provider_id: provider_id.to_owned(),
provider_reference: image.to_owned(),
spread_config: p.to_owned(),
model_name: application_name.to_owned(),
provider_config: config_names,
},
component_name,
),
notifier.clone(),
config_scalers,
secret_scalers,
notifier_subject,
application_name,
// Providers are a bit longer because it can take a bit to download
Some(Duration::from_secs(60)),
)) as BoxedScaler)
}
// Find the target component of the link and create a scaler for it.
(LINK_TRAIT, TraitProperty::Link(p), _) => {
components
.iter()
.find_map(|component| match &component.properties {
// Providers cannot link to other providers, only components
Properties::Capability { .. } if component.name == p.target.name => {
error!(
"Provider {} cannot link to provider {}, only components",
&component.name, p.target.name
);
None
}
Properties::Component {
properties:
ComponentProperties {
image,
application,
id,
..
},
} if component.name == p.target.name => Some(link_scaler(
p,
lattice_id,
manifest_name,
application_name,
&component.name,
provider_id.to_owned(),
id.as_ref(),
image.as_ref(),
application.as_ref(),
policies,
notifier_subject,
notifier,
snapshot_data,
)),
_ => None,
})
}
_ => None,
}
}));
// Allow providers to omit the spreadscaler entirely for simplicity
if !scaler_specified {
if let Some(image) = &properties.image {
let (config_scalers, mut config_names) =
config_to_scalers(snapshot_data, application_name, &properties.config);
let (secret_scalers, mut secret_names) = secrets_to_scalers(
snapshot_data,
application_name,
&properties.secrets,
policies,
);
config_names.append(&mut secret_names);
scalers.push(Box::new(BackoffWrapper::new(
ProviderSpreadScaler::new(
snapshot_data.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_owned(),
provider_id,
provider_reference: image.to_owned(),
spread_config: SpreadScalerProperty {
instances: 1,
spread: vec![],
},
model_name: application_name.to_owned(),
provider_config: config_names,
},
component_name,
),
notifier.clone(),
config_scalers,
secret_scalers,
notifier_subject,
application_name,
// Providers are a bit longer because it can take a bit to download
Some(Duration::from_secs(60)),
)) as BoxedScaler)
}
}
}
/// Resolves configuration, secrets, and the target of a link to create a boxed [`LinkScaler`]
///
/// # Arguments
/// * `link_property` - The properties of the link to convert
/// * `lattice_id` - The lattice id the scalers operate on
/// * `manifest_name` - The name of the manifest that the scalers are being created for
/// * `application_name` - The name of the application that the scalers are being created for
/// * `component_name` - The name of the component to convert
/// * `source_id` - The ID of the source component
/// * `target_id` - The optional ID of the target component
/// * `image` - The optional image reference of the target component
/// * `shared` - The optional shared application reference of the target component
/// * `policies` - The policies to use when creating the scalers so they can access secrets
/// * `notifier_subject` - The subject to use when creating the scalers so they can report status
/// * `notifier` - The publisher to use when creating the scalers so they can report status
/// * `snapshot_data` - The store to use when creating the scalers so they can access lattice state
#[allow(clippy::too_many_arguments)]
fn link_scaler<S, P, L>(
link_property: &LinkProperty,
lattice_id: &str,
manifest_name: &str,
application_name: &str,
component_name: &str,
source_id: String,
target_id: Option<&String>,
image: Option<&String>,
shared: Option<&SharedApplicationComponentProperties>,
policies: &HashMap<&String, &Policy>,
notifier_subject: &str,
notifier: &P,
snapshot_data: &SnapshotStore<S, L>,
) -> BoxedScaler
where
S: ReadStore + Send + Sync + Clone + 'static,
P: Publisher + Clone + Send + Sync + 'static,
L: LinkSource + ConfigSource + SecretSource + Clone + Send + Sync + 'static,
{
let (mut config_scalers, mut source_config) = config_to_scalers(
snapshot_data,
manifest_name,
&link_property
.source
.as_ref()
.unwrap_or(&Default::default())
.config,
);
let (target_config_scalers, mut target_config) =
config_to_scalers(snapshot_data, manifest_name, &link_property.target.config);
let (target_secret_scalers, target_secrets) = secrets_to_scalers(
snapshot_data,
manifest_name,
&link_property.target.secrets,
policies,
);
let (mut source_secret_scalers, source_secrets) = secrets_to_scalers(
snapshot_data,
manifest_name,
&link_property
.source
.as_ref()
.unwrap_or(&Default::default())
.secrets,
policies,
);
config_scalers.extend(target_config_scalers);
source_secret_scalers.extend(target_secret_scalers);
target_config.extend(target_secrets);
source_config.extend(source_secrets);
let (target_manifest_name, target_component_name) =
match resolve_manifest_component(manifest_name, component_name, image, shared) {
Ok(name) => name,
Err(err) => {
error!(err);
return Box::new(StatusScaler::new(
uuid::Uuid::new_v4().to_string(),
LINK_SCALER_KIND,
format!(
"{} -({}:{})-> {}",
component_name,
link_property.namespace,
link_property.package,
link_property.target.name
),
StatusInfo::failed(err),
)) as BoxedScaler;
}
};
let target = compute_component_id(target_manifest_name, target_id, target_component_name);
Box::new(BackoffWrapper::new(
LinkScaler::new(
snapshot_data.clone(),
LinkScalerConfig {
source_id,
target,
wit_namespace: link_property.namespace.to_owned(),
wit_package: link_property.package.to_owned(),
wit_interfaces: link_property.interfaces.to_owned(),
name: link_property
.name
.to_owned()
.unwrap_or_else(|| DEFAULT_LINK_NAME.to_string()),
lattice_id: lattice_id.to_owned(),
model_name: application_name.to_owned(),
source_config,
target_config,
},
snapshot_data.clone(),
),
notifier.clone(),
config_scalers,
source_secret_scalers,
notifier_subject,
application_name,
Some(Duration::from_secs(5)),
)) as BoxedScaler
}
/// Returns a tuple which is a list of scalers and a list of the names of the configs that the
/// scalers use.
///
/// Any input [ConfigProperty] that has a `properties` field will be converted into a [ConfigScaler], and
/// the name of the configuration will be modified to be unique to the model and component. If the properties
/// field is not present, the name will be used as-is and assumed that it's managed externally to wadm.
fn config_to_scalers<C: ConfigSource + Send + Sync + Clone>(
config_source: &C,
manifest_name: &str,
configs: &[ConfigProperty],
) -> (Vec<ConfigScaler<C>>, Vec<String>) {
configs
.iter()
.map(|config| {
let name = if config.properties.is_some() {
compute_component_id(manifest_name, None, &config.name)
} else {
config.name.clone()
};
(
ConfigScaler::new(config_source.clone(), &name, config.properties.as_ref()),
name,
)
})
.unzip()
}
fn secrets_to_scalers<S: SecretSource + Send + Sync + Clone>(
secret_source: &S,
manifest_name: &str,
secrets: &[SecretProperty],
policies: &HashMap<&String, &Policy>,
) -> (Vec<SecretScaler<S>>, Vec<String>) {
secrets
.iter()
.map(|s| {
let name = compute_secret_id(manifest_name, None, &s.name);
let policy = *policies.get(&s.properties.policy).unwrap();
(
SecretScaler::new(
name.clone(),
policy.clone(),
s.clone(),
secret_source.clone(),
),
name,
)
})
.unzip()
}
/// Based on the name of the model and the optionally provided ID, returns a unique ID for the
/// component that is a sanitized version of the component reference and model name, separated
/// by a dash.
pub(crate) fn compute_component_id(
manifest_name: &str,
component_id: Option<&String>,
component_name: &str,
) -> String {
if let Some(id) = component_id {
id.to_owned()
} else {
format!(
"{}-{}",
manifest_name
.to_lowercase()
.replace(|c: char| !c.is_ascii_alphanumeric(), "_"),
component_name
.to_lowercase()
.replace(|c: char| !c.is_ascii_alphanumeric(), "_")
)
}
}
pub(crate) fn compute_secret_id(
manifest_name: &str,
component_id: Option<&String>,
component_name: &str,
) -> String {
let name = compute_component_id(manifest_name, component_id, component_name);
format!("{SECRET_PREFIX}_{name}")
}
/// Helper function to resolve a link to a manifest component, returning the name of the manifest
/// and the name of the component where the target resides.
///
/// If the component resides in the same manifest, then the name of the manifest & the name of the
/// component as specified will be returned. In the case that the component resides in a shared
/// application, the name of the shared application & the name of the component in that application
/// will be returned.
///
/// # Arguments
/// * `application_name` - The name of the manifest that the scalers are being created for
/// * `component_name` - The name of the component in the source manifest to target
/// * `component_image_ref` - The image reference for the component
/// * `shared_app_info` - The optional shared application reference for the component
fn resolve_manifest_component<'a>(
application_name: &'a str,
component_name: &'a str,
component_image_ref: Option<&'a String>,
shared_app_info: Option<&'a SharedApplicationComponentProperties>,
) -> Result<(&'a str, &'a str), &'a str> {
match (component_image_ref, shared_app_info) {
(Some(_), None) => Ok((application_name, component_name)),
(None, Some(app)) => Ok((app.name.as_str(), app.component.as_str())),
// These two cases should both be unreachable, since this is caught at manifest
// validation before it's put. Just in case, we'll log an error and ensure the status is failed
(None, None) => Err("Application did not specify an image or shared application reference"),
(Some(_image), Some(_app)) => {
Err("Application specified both an image and a shared application reference")
}
}
}
#[cfg(test)]
mod test {
use super::compute_component_id;
#[test]
fn compute_proper_component_id() {
// User supplied ID always takes precedence
assert_eq!(
compute_component_id("mymodel", Some(&"myid".to_string()), "echo"),
"myid"
);
assert_eq!(
compute_component_id(
"some model name with spaces cause yaml",
Some(&"myid".to_string()),
" echo "
),
"myid"
);
// Sanitize component reference
assert_eq!(
compute_component_id("mymodel", None, "echo-component"),
"mymodel-echo_component"
);
// Ensure we can support spaces in the model name, because YAML strings
assert_eq!(
compute_component_id("some model name with spaces cause yaml", None, "echo"),
"some_model_name_with_spaces_cause_yaml-echo"
);
// Ensure we can support spaces in the model name, because YAML strings
// Ensure we can support lowercasing the reference as well, just in case
assert_eq!(
compute_component_id("My ThInG", None, "thing.wasm"),
"my_thing-thing_wasm"
);
}
}

View File

@ -7,6 +7,7 @@ use tokio::sync::RwLock;
use tracing::{instrument, trace};
use wadm_types::{api::StatusInfo, Spread, SpreadScalerProperty, TraitProperty};
use crate::events::ConfigSet;
use crate::scaler::spreadscaler::{
compute_ineligible_hosts, eligible_hosts, spreadscaler_annotations,
};
@ -22,7 +23,7 @@ use super::compute_id_sha256;
pub mod provider;
// Annotation constants
pub const COMPONENT_DAEMON_SCALER_TYPE: &str = "componentdaemonscaler";
pub const DAEMON_SCALER_KIND: &str = "DaemonScaler";
/// Config for a ComponentDaemonScaler
#[derive(Clone, Debug)]
@ -58,6 +59,14 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentDaemonScaler<S> {
&self.id
}
fn kind(&self) -> &str {
DAEMON_SCALER_KIND
}
fn name(&self) -> String {
self.spread_config.component_id.to_string()
}
async fn status(&self) -> StatusInfo {
let _ = self.reconcile().await;
self.status.read().await.to_owned()
@ -111,6 +120,9 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentDaemonScaler<S> {
Ok(Vec::new())
}
}
Event::ConfigSet(ConfigSet { config_name }) if self.config.contains(config_name) => {
self.reconcile().await
}
// No other event impacts the job of this scaler so we can ignore it
_ => Ok(Vec::new()),
}
@ -251,9 +263,14 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentDaemonScaler<S> {
trace!(?commands, "Calculated commands for component daemon scaler");
let status = match (spread_status.is_empty(), commands.is_empty()) {
// No failures, no commands, scaler satisfied
(true, true) => StatusInfo::deployed(""),
(_, false) => StatusInfo::reconciling(""),
(false, true) => StatusInfo::failed(
// No failures, commands generated, scaler is reconciling
(true, false) => {
StatusInfo::reconciling(&format!("Scaling component on {} host(s)", commands.len()))
}
// Failures occurred, scaler is in a failed state
(false, _) => StatusInfo::failed(
&spread_status
.into_iter()
.map(|s| s.message)
@ -301,7 +318,7 @@ impl<S: ReadStore + Send + Sync> ComponentDaemonScaler<S> {
// that make it unique. This is used during upgrades to determine if a
// scaler is the same as a previous one.
let mut id_parts = vec![
COMPONENT_DAEMON_SCALER_TYPE,
DAEMON_SCALER_KIND,
&model_name,
component_name,
&component_id,
@ -344,10 +361,10 @@ mod test {
sync::Arc,
};
use anyhow::Result;
use anyhow::{anyhow, Result};
use chrono::Utc;
use wadm_types::{api::StatusType, Spread, SpreadScalerProperty};
use wasmcloud_control_interface::{HostInventory, InterfaceLinkDefinition};
use wasmcloud_control_interface::{HostInventory, Link};
use crate::{
commands::Command,
@ -781,20 +798,19 @@ mod test {
// Inserting for heartbeat handling later
lattice_source.inventory.write().await.insert(
host_id_three.to_string(),
HostInventory {
components: vec![],
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
HostInventory::builder()
.friendly_name("hey".into())
.labels(BTreeMap::from_iter([
("cloud".to_string(), "purgatory".to_string()),
("location".to_string(), "edge".to_string()),
("region".to_string(), "us-brooks-1".to_string()),
]),
providers: vec![],
host_id: host_id_three.to_string(),
version: "1.0.0".to_string(),
uptime_human: "what is time really anyway maaaan".to_string(),
uptime_seconds: 42,
},
]))
.host_id(host_id_three.into())
.version("1.0.0".into())
.uptime_human("what is time really anyway maaaan".into())
.uptime_seconds(42)
.build()
.map_err(|e| anyhow!("failed to build host inventory: {e}"))?,
);
let command_publisher = CommandPublisher::new(NoopPublisher, "doesntmatter");
let status_publisher = StatusPublisher::new(NoopPublisher, None, "doesntmatter");
@ -944,7 +960,7 @@ mod test {
.is_empty());
assert!(blobby_daemonscaler
.handle_event(&Event::LinkdefSet(LinkdefSet {
linkdef: InterfaceLinkDefinition::default()
linkdef: Link::default()
}))
.await?
.is_empty());

View File

@ -4,15 +4,20 @@ use anyhow::Result;
use async_trait::async_trait;
use tokio::sync::RwLock;
use tracing::{instrument, trace};
use wadm_types::api::StatusType;
use wadm_types::{api::StatusInfo, Spread, SpreadScalerProperty, TraitProperty};
use crate::commands::StopProvider;
use crate::events::{HostHeartbeat, ProviderInfo, ProviderStarted, ProviderStopped};
use crate::events::{
ConfigSet, HostHeartbeat, ProviderHealthCheckFailed, ProviderHealthCheckInfo,
ProviderHealthCheckPassed, ProviderInfo, ProviderStarted, ProviderStopped,
};
use crate::scaler::compute_id_sha256;
use crate::scaler::spreadscaler::{
compute_ineligible_hosts, eligible_hosts, provider::ProviderSpreadConfig,
spreadscaler_annotations,
};
use crate::storage::{Provider, ProviderStatus};
use crate::SCALER_KEY;
use crate::{
commands::{Command, StartProvider},
@ -21,8 +26,7 @@ use crate::{
storage::{Host, ReadStore},
};
// Annotation constants
pub const PROVIDER_DAEMON_SCALER_TYPE: &str = "providerdaemonscaler";
use super::DAEMON_SCALER_KIND;
/// The ProviderDaemonScaler ensures that a provider is running on every host, according to a
/// [SpreadScalerProperty](crate::model::SpreadScalerProperty)
@ -42,6 +46,14 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ProviderDaemonScaler<S> {
&self.id
}
fn kind(&self) -> &str {
DAEMON_SCALER_KIND
}
fn name(&self) -> String {
self.config.provider_id.to_string()
}
async fn status(&self) -> StatusInfo {
let _ = self.reconcile().await;
self.status.read().await.to_owned()
@ -90,6 +102,65 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ProviderDaemonScaler<S> {
{
self.reconcile().await
}
// perform status updates for health check events
Event::ProviderHealthCheckFailed(ProviderHealthCheckFailed {
data: ProviderHealthCheckInfo { provider_id, .. },
})
| Event::ProviderHealthCheckPassed(ProviderHealthCheckPassed {
data: ProviderHealthCheckInfo { provider_id, .. },
}) if provider_id == &self.config.provider_id => {
let provider = self
.store
.get::<Provider>(&self.config.lattice_id, &self.config.provider_id)
.await?;
let unhealthy_providers = provider.map_or(0, |p| {
p.hosts
.values()
.filter(|s| *s == &ProviderStatus::Failed)
.count()
});
let status = self.status.read().await.to_owned();
// update health status of scaler
if let Some(status) = match (status, unhealthy_providers > 0) {
// scaler is deployed but contains unhealthy providers
(
StatusInfo {
status_type: StatusType::Deployed,
..
},
true,
) => Some(StatusInfo::failed(&format!(
"Unhealthy provider on {} host(s)",
unhealthy_providers
))),
// scaler can become unhealthy only if it was previously deployed
// once scaler becomes healthy again revert back to deployed state
// this is a workaround to detect unhealthy status until
// StatusType::Unhealthy can be used
(
StatusInfo {
status_type: StatusType::Failed,
message,
},
false,
) if message.starts_with("Unhealthy provider on") => {
Some(StatusInfo::deployed(""))
}
// don't update status if scaler is not deployed
_ => None,
} {
*self.status.write().await = status;
}
// only status needs update no new commands required
Ok(Vec::new())
}
Event::ConfigSet(ConfigSet { config_name })
if self.config.provider_config.contains(config_name) =>
{
self.reconcile().await
}
// No other event impacts the job of this scaler so we can ignore it
_ => Ok(Vec::new()),
}
@ -98,7 +169,6 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ProviderDaemonScaler<S> {
#[instrument(level = "trace", skip_all, fields(name = %self.config.model_name, scaler_id = %self.id))]
async fn reconcile(&self) -> Result<Vec<Command>> {
let hosts = self.store.list::<Host>(&self.config.lattice_id).await?;
let provider_id = &self.config.provider_id;
let provider_ref = &self.config.provider_reference;
@ -207,9 +277,14 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ProviderDaemonScaler<S> {
trace!(?commands, "Calculated commands for provider daemonscaler");
let status = match (spread_status.is_empty(), commands.is_empty()) {
// No failures, no commands, scaler satisfied
(true, true) => StatusInfo::deployed(""),
(_, false) => StatusInfo::reconciling(""),
(false, true) => StatusInfo::failed(
// No failures, commands generated, scaler is reconciling
(true, false) => {
StatusInfo::reconciling(&format!("Scaling provider on {} host(s)", commands.len()))
}
// Failures occurred, scaler is in a failed state
(false, _) => StatusInfo::failed(
&spread_status
.into_iter()
.map(|s| s.message)
@ -246,7 +321,7 @@ impl<S: ReadStore + Send + Sync> ProviderDaemonScaler<S> {
// that make it unique. This is used during upgrades to determine if a
// scaler is the same as a previous one.
let mut id_parts = vec![
PROVIDER_DAEMON_SCALER_TYPE,
DAEMON_SCALER_KIND,
&config.model_name,
component_name,
&config.provider_id,
@ -493,4 +568,274 @@ mod test {
Ok(())
}
#[tokio::test]
async fn test_healthy_providers_return_healthy_status() -> Result<()> {
let lattice_id = "test_healthy_providers";
let provider_ref = "fakecloud.azurecr.io/provider:3.2.1".to_string();
let provider_id = "VASDASDIAMAREALPROVIDERPROVIDER";
let host_id_one = "NASDASDIMAREALHOSTONE";
let host_id_two = "NASDASDIMAREALHOSTTWO";
let store = Arc::new(TestStore::default());
store
.store(
lattice_id,
host_id_one.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("inda".to_string(), "cloud".to_string()),
("cloud".to_string(), "fake".to_string()),
("region".to_string(), "us-noneofyourbusiness-1".to_string()),
]),
providers: HashSet::from_iter([ProviderInfo {
provider_id: provider_id.to_string(),
provider_ref: provider_ref.to_string(),
annotations: BTreeMap::default(),
}]),
uptime_seconds: 123,
version: None,
id: host_id_one.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
host_id_two.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("inda".to_string(), "cloud".to_string()),
("cloud".to_string(), "real".to_string()),
("region".to_string(), "us-yourhouse-1".to_string()),
]),
providers: HashSet::from_iter([ProviderInfo {
provider_id: provider_id.to_string(),
provider_ref: provider_ref.to_string(),
annotations: BTreeMap::default(),
}]),
uptime_seconds: 123,
version: None,
id: host_id_two.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
provider_id.to_string(),
Provider {
id: provider_id.to_string(),
name: "provider".to_string(),
issuer: "issuer".to_string(),
reference: provider_ref.to_string(),
hosts: HashMap::from([
(host_id_one.to_string(), ProviderStatus::Failed),
(host_id_two.to_string(), ProviderStatus::Running),
]),
},
)
.await?;
// Ensure we spread evenly with equal weights, clean division
let multi_spread_even = SpreadScalerProperty {
// instances are ignored so putting an absurd number
instances: 2,
spread: vec![Spread {
name: "SimpleOne".to_string(),
requirements: BTreeMap::from_iter([("inda".to_string(), "cloud".to_string())]),
weight: Some(100),
}],
};
let spreadscaler = ProviderDaemonScaler::new(
store.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_string(),
provider_id: provider_id.to_string(),
provider_reference: provider_ref.to_string(),
spread_config: multi_spread_even,
model_name: MODEL_NAME.to_string(),
provider_config: vec!["foobar".to_string()],
},
"fake_component",
);
spreadscaler.reconcile().await?;
spreadscaler
.handle_event(&Event::ProviderHealthCheckFailed(
ProviderHealthCheckFailed {
data: ProviderHealthCheckInfo {
provider_id: provider_id.to_string(),
host_id: host_id_one.to_string(),
},
},
))
.await?;
store
.store(
lattice_id,
provider_id.to_string(),
Provider {
id: provider_id.to_string(),
name: "provider".to_string(),
issuer: "issuer".to_string(),
reference: provider_ref.to_string(),
hosts: HashMap::from([
(host_id_one.to_string(), ProviderStatus::Pending),
(host_id_two.to_string(), ProviderStatus::Running),
]),
},
)
.await?;
spreadscaler
.handle_event(&Event::ProviderHealthCheckPassed(
ProviderHealthCheckPassed {
data: ProviderHealthCheckInfo {
provider_id: provider_id.to_string(),
host_id: host_id_two.to_string(),
},
},
))
.await?;
assert_eq!(
spreadscaler.status.read().await.to_owned(),
StatusInfo::deployed("")
);
Ok(())
}
#[tokio::test]
async fn test_unhealthy_providers_return_unhealthy_status() -> Result<()> {
let lattice_id = "test_unhealthy_providers";
let provider_ref = "fakecloud.azurecr.io/provider:3.2.1".to_string();
let provider_id = "VASDASDIAMAREALPROVIDERPROVIDER";
let host_id_one = "NASDASDIMAREALHOSTONE";
let host_id_two = "NASDASDIMAREALHOSTTWO";
let store = Arc::new(TestStore::default());
store
.store(
lattice_id,
host_id_one.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("inda".to_string(), "cloud".to_string()),
("cloud".to_string(), "fake".to_string()),
("region".to_string(), "us-noneofyourbusiness-1".to_string()),
]),
providers: HashSet::from_iter([ProviderInfo {
provider_id: provider_id.to_string(),
provider_ref: provider_ref.to_string(),
annotations: BTreeMap::default(),
}]),
uptime_seconds: 123,
version: None,
id: host_id_one.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
host_id_two.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("inda".to_string(), "cloud".to_string()),
("cloud".to_string(), "real".to_string()),
("region".to_string(), "us-yourhouse-1".to_string()),
]),
providers: HashSet::from_iter([ProviderInfo {
provider_id: provider_id.to_string(),
provider_ref: provider_ref.to_string(),
annotations: BTreeMap::default(),
}]),
uptime_seconds: 123,
version: None,
id: host_id_two.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
provider_id.to_string(),
Provider {
id: provider_id.to_string(),
name: "provider".to_string(),
issuer: "issuer".to_string(),
reference: provider_ref.to_string(),
hosts: HashMap::from([
(host_id_one.to_string(), ProviderStatus::Failed),
(host_id_two.to_string(), ProviderStatus::Running),
]),
},
)
.await?;
// Ensure we spread evenly with equal weights, clean division
let multi_spread_even = SpreadScalerProperty {
// instances are ignored so putting an absurd number
instances: 2,
spread: vec![Spread {
name: "SimpleOne".to_string(),
requirements: BTreeMap::from_iter([("inda".to_string(), "cloud".to_string())]),
weight: Some(100),
}],
};
let spreadscaler = ProviderDaemonScaler::new(
store.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_string(),
provider_id: provider_id.to_string(),
provider_reference: provider_ref.to_string(),
spread_config: multi_spread_even,
model_name: MODEL_NAME.to_string(),
provider_config: vec!["foobar".to_string()],
},
"fake_component",
);
spreadscaler.reconcile().await?;
spreadscaler
.handle_event(&Event::ProviderHealthCheckFailed(
ProviderHealthCheckFailed {
data: ProviderHealthCheckInfo {
provider_id: provider_id.to_string(),
host_id: host_id_one.to_string(),
},
},
))
.await?;
assert_eq!(
spreadscaler.status.read().await.to_owned(),
StatusInfo::failed("Unhealthy provider on 1 host(s)")
);
Ok(())
}
}

View File

@ -1,6 +1,6 @@
//! A struct that manages creating and removing scalers for all manifests
use std::{collections::HashMap, ops::Deref, sync::Arc, time::Duration};
use std::{collections::HashMap, ops::Deref, sync::Arc};
use anyhow::Result;
use async_nats::jetstream::{
@ -18,32 +18,19 @@ use tokio::{
};
use tracing::{debug, error, instrument, trace, warn};
use wadm_types::{
api::StatusInfo, CapabilityProperties, Component, ComponentProperties, ConfigProperty,
Manifest, Policy, Properties, SecretProperty, SpreadScalerProperty, Trait, TraitProperty,
DAEMONSCALER_TRAIT, LINK_TRAIT, SPREADSCALER_TRAIT,
api::{Status, StatusInfo},
Manifest,
};
use crate::{
events::Event,
publisher::Publisher,
scaler::{
secretscaler::SECRET_CONFIG_PREFIX, spreadscaler::ComponentSpreadScaler, Command, Scaler,
},
scaler::{Command, Scaler},
storage::{snapshot::SnapshotStore, ReadStore},
workers::{CommandPublisher, ConfigSource, LinkSource, SecretSource, StatusPublisher},
DEFAULT_LINK_NAME,
};
use super::{
configscaler::ConfigScaler,
daemonscaler::{provider::ProviderDaemonScaler, ComponentDaemonScaler},
secretscaler::SecretScaler,
spreadscaler::{
link::{LinkScaler, LinkScalerConfig},
provider::{ProviderSpreadConfig, ProviderSpreadScaler},
},
BackoffAwareScaler,
};
use super::convert::manifest_components_to_scalers;
pub type BoxedScaler = Box<dyn Scaler + Send + Sync + 'static>;
pub type ScalerList = Vec<BoxedScaler>;
@ -183,7 +170,9 @@ where
.list(multitenant_prefix, lattice_id)
.await?
.into_iter()
.map(|summary| manifest_store.get(multitenant_prefix, lattice_id, summary.name));
.map(|summary| {
manifest_store.get(multitenant_prefix, lattice_id, summary.name().to_owned())
});
let all_manifests = futures::future::join_all(futs)
.await
.into_iter()
@ -200,13 +189,13 @@ where
.filter_map(|manifest| {
let data = manifest.get_deployed()?;
let name = manifest.name().to_owned();
let scalers = components_to_scalers(
let scalers = manifest_components_to_scalers(
&data.spec.components,
&data.policy_lookup(),
lattice_id,
&client,
&name,
&subject,
&client,
&snapshot_data,
);
Some((name, scalers))
@ -291,13 +280,13 @@ where
}
pub fn scalers_for_manifest<'a>(&'a self, manifest: &'a Manifest) -> ScalerList {
components_to_scalers(
manifest_components_to_scalers(
&manifest.spec.components,
&manifest.policy_lookup(),
&self.lattice_id,
&self.client,
&manifest.metadata.name,
&self.subject,
&self.client,
&self.snapshot_data,
)
}
@ -390,11 +379,13 @@ where
/// Does everything except sending the notification
#[instrument(level = "debug", skip(self), fields(lattice_id = %self.lattice_id))]
async fn remove_scalers_internal(&self, name: &str) -> Option<Result<ScalerList>> {
// Always refresh data before removing
// Remove the scalers first to avoid them handling events while we're cleaning up
let scalers = self.remove_raw_scalers(name).await?;
// Always refresh data before cleaning up
if let Err(e) = self.refresh_data().await {
return Some(Err(e));
}
let scalers = self.remove_raw_scalers(name).await?;
let commands = match futures::future::join_all(
scalers.iter().map(|scaler| scaler.cleanup()),
)
@ -439,13 +430,13 @@ where
match notification {
Notifications::CreateScalers(manifest) => {
// We don't want to trigger the notification, so just create the scalers and then insert
let scalers = components_to_scalers(
let scalers = manifest_components_to_scalers(
&manifest.spec.components,
&manifest.policy_lookup(),
&self.lattice_id,
&self.client,
&manifest.metadata.name,
&self.subject,
&self.client,
&self.snapshot_data,
);
let num_scalers = scalers.len();
@ -466,7 +457,10 @@ where
// hasn't deleted the scaler yet
if let Err(e) = self
.status_publisher
.publish_status(&name, StatusInfo::undeployed(""))
.publish_status(&name, Status::new(
StatusInfo::undeployed("Manifest has been undeployed"),
Vec::with_capacity(0),
))
.await
{
warn!(error = ?e, "Failed to set status to undeployed");
@ -497,7 +491,7 @@ where
// wrapped ones (which is good from a Rust API point of view). If
// this starts to become a problem, we can revisit how we handle
// this (probably by requiring that this struct always wraps any
// scaler in the backoff scaler and using custom methods from that
// scaler in the backoff wrapper and using custom methods from that
// type)
Notifications::RegisterExpectedEvents{ name, scaler_id, triggering_event } => {
trace!(%name, "Computing and registering expected events for manifest");
@ -559,471 +553,3 @@ where
}
}
}
const EMPTY_TRAIT_VEC: Vec<Trait> = Vec::new();
/// Converts a list of components into a list of scalers
///
/// # Arguments
/// * `components` - The list of components to convert
/// * `store` - The store to use when creating the scalers so they can access lattice state
/// * `lattice_id` - The lattice id the scalers operate on
/// * `name` - The name of the manifest that the scalers are being created for
pub(crate) fn components_to_scalers<S, P, L>(
components: &[Component],
policies: &HashMap<&String, &Policy>,
lattice_id: &str,
notifier: &P,
name: &str,
notifier_subject: &str,
snapshot_data: &SnapshotStore<S, L>,
) -> ScalerList
where
S: ReadStore + Send + Sync + Clone + 'static,
P: Publisher + Clone + Send + Sync + 'static,
L: LinkSource + ConfigSource + SecretSource + Clone + Send + Sync + 'static,
{
let mut scalers: ScalerList = Vec::new();
for component in components.iter() {
let traits = component.traits.as_ref();
match &component.properties {
Properties::Component { properties: props } => {
scalers.extend(traits.unwrap_or(&EMPTY_TRAIT_VEC).iter().filter_map(|trt| {
let component_id =
compute_component_id(name, props.id.as_ref(), &component.name);
let (config_scalers, mut config_names) =
config_to_scalers(snapshot_data.clone(), name, &props.config);
let (secret_scalers, secret_names) =
secrets_to_scalers(snapshot_data.clone(), name, &props.secrets, policies);
config_names.append(&mut secret_names.clone());
match (trt.trait_type.as_str(), &trt.properties) {
(SPREADSCALER_TRAIT, TraitProperty::SpreadScaler(p)) => {
Some(Box::new(BackoffAwareScaler::new(
ComponentSpreadScaler::new(
snapshot_data.clone(),
props.image.to_owned(),
component_id,
lattice_id.to_owned(),
name.to_owned(),
p.to_owned(),
&component.name,
config_names,
),
notifier.to_owned(),
config_scalers,
secret_scalers,
notifier_subject,
name,
Some(Duration::from_secs(5)),
)) as BoxedScaler)
}
(DAEMONSCALER_TRAIT, TraitProperty::SpreadScaler(p)) => {
Some(Box::new(BackoffAwareScaler::new(
ComponentDaemonScaler::new(
snapshot_data.clone(),
props.image.to_owned(),
component_id,
lattice_id.to_owned(),
name.to_owned(),
p.to_owned(),
&component.name,
config_names,
),
notifier.to_owned(),
config_scalers,
secret_scalers,
notifier_subject,
name,
Some(Duration::from_secs(5)),
)) as BoxedScaler)
}
(LINK_TRAIT, TraitProperty::Link(p)) => {
components.iter().find_map(|component| {
let (mut config_scalers, mut source_config) = config_to_scalers(
snapshot_data.clone(),
name,
&p.source.as_ref().unwrap_or(&Default::default()).config,
);
let (target_config_scalers, mut target_config) = config_to_scalers(
snapshot_data.clone(),
name,
&p.target.config,
);
let (target_secret_scalers, target_secrets) = secrets_to_scalers(
snapshot_data.clone(),
name,
&p.target.secrets,
policies,
);
let (mut source_secret_scalers, source_secrets) =
secrets_to_scalers(
snapshot_data.clone(),
name,
&p.source.as_ref().unwrap_or(&Default::default()).secrets,
policies,
);
config_scalers.extend(target_config_scalers);
source_secret_scalers.extend(target_secret_scalers);
target_config.extend(target_secrets);
source_config.extend(source_secrets);
match &component.properties {
Properties::Capability {
properties: CapabilityProperties { id, .. },
}
| Properties::Component {
properties: ComponentProperties { id, .. },
} if component.name == p.target.name => {
Some(Box::new(BackoffAwareScaler::new(
LinkScaler::new(
snapshot_data.clone(),
LinkScalerConfig {
source_id: component_id.to_string(),
target: compute_component_id(
name,
id.as_ref(),
&component.name,
),
wit_namespace: p.namespace.to_owned(),
wit_package: p.package.to_owned(),
wit_interfaces: p.interfaces.to_owned(),
name: p.name.to_owned().unwrap_or_else(|| {
DEFAULT_LINK_NAME.to_string()
}),
lattice_id: lattice_id.to_owned(),
model_name: name.to_owned(),
source_config,
target_config,
},
snapshot_data.clone(),
),
notifier.to_owned(),
config_scalers,
source_secret_scalers,
notifier_subject,
name,
Some(Duration::from_secs(5)),
))
as BoxedScaler)
}
_ => None,
}
})
}
_ => None,
}
}))
}
Properties::Capability { properties: props } => {
let provider_id = compute_component_id(name, props.id.as_ref(), &component.name);
let mut scaler_specified = false;
if let Some(traits) = traits {
scalers.extend(traits.iter().filter_map(|trt| {
match (trt.trait_type.as_str(), &trt.properties) {
(SPREADSCALER_TRAIT, TraitProperty::SpreadScaler(p)) => {
scaler_specified = true;
let (config_scalers, mut config_names) =
config_to_scalers(snapshot_data.clone(), name, &props.config);
let (secret_scalers, secret_names) = secrets_to_scalers(
snapshot_data.clone(),
name,
&props.secrets,
policies,
);
config_names.append(&mut secret_names.clone());
Some(Box::new(BackoffAwareScaler::new(
ProviderSpreadScaler::new(
snapshot_data.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_owned(),
provider_id: provider_id.to_owned(),
provider_reference: props.image.to_owned(),
spread_config: p.to_owned(),
model_name: name.to_owned(),
provider_config: config_names,
},
&component.name,
),
notifier.to_owned(),
config_scalers,
secret_scalers,
notifier_subject,
name,
// Providers are a bit longer because it can take a bit to download
Some(Duration::from_secs(60)),
)) as BoxedScaler)
}
(DAEMONSCALER_TRAIT, TraitProperty::SpreadScaler(p)) => {
scaler_specified = true;
let (config_scalers, mut config_names) =
config_to_scalers(snapshot_data.clone(), name, &props.config);
let (secret_scalers, secret_names) = secrets_to_scalers(
snapshot_data.clone(),
name,
&props.secrets,
policies,
);
config_names.append(&mut secret_names.clone());
Some(Box::new(BackoffAwareScaler::new(
ProviderDaemonScaler::new(
snapshot_data.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_owned(),
provider_id: provider_id.to_owned(),
provider_reference: props.image.to_owned(),
spread_config: p.to_owned(),
model_name: name.to_owned(),
provider_config: config_names,
},
&component.name,
),
notifier.to_owned(),
config_scalers,
secret_scalers,
notifier_subject,
name,
// Providers are a bit longer because it can take a bit to download
Some(Duration::from_secs(60)),
)) as BoxedScaler)
}
(LINK_TRAIT, TraitProperty::Link(p)) => {
components.iter().find_map(|component| {
let (mut config_scalers, mut source_config) = config_to_scalers(
snapshot_data.clone(),
name,
&p.source.as_ref().unwrap_or(&Default::default()).config,
);
let (target_config_scalers, mut target_config) =
config_to_scalers(
snapshot_data.clone(),
name,
&p.target.config,
);
let (target_secret_scalers, target_secrets) =
secrets_to_scalers(
snapshot_data.clone(),
name,
&p.target.secrets,
policies,
);
let (mut source_secret_scalers, source_secrets) =
secrets_to_scalers(
snapshot_data.clone(),
name,
&p.source
.as_ref()
.unwrap_or(&Default::default())
.secrets,
policies,
);
config_scalers.extend(target_config_scalers);
source_secret_scalers.extend(target_secret_scalers);
target_config.extend(target_secrets);
source_config.extend(source_secrets);
match &component.properties {
Properties::Component { properties: cappy }
if component.name == p.target.name =>
{
Some(Box::new(BackoffAwareScaler::new(
LinkScaler::new(
snapshot_data.clone(),
LinkScalerConfig {
source_id: provider_id.to_string(),
target: compute_component_id(
name,
cappy.id.as_ref(),
&component.name,
),
wit_namespace: p.namespace.to_owned(),
wit_package: p.package.to_owned(),
wit_interfaces: p.interfaces.to_owned(),
name: p.name.to_owned().unwrap_or_else(
|| DEFAULT_LINK_NAME.to_string(),
),
lattice_id: lattice_id.to_owned(),
model_name: name.to_owned(),
source_config,
target_config,
},
snapshot_data.clone(),
),
notifier.to_owned(),
config_scalers,
source_secret_scalers,
notifier_subject,
name,
Some(Duration::from_secs(5)),
))
as BoxedScaler)
}
_ => None,
}
})
}
_ => None,
}
}))
}
// Allow providers to omit the scaler entirely for simplicity
if !scaler_specified {
let (config_scalers, mut config_names) =
config_to_scalers(snapshot_data.clone(), name, &props.config);
let (secret_scalers, secret_names) =
secrets_to_scalers(snapshot_data.clone(), name, &props.secrets, policies);
config_names.append(&mut secret_names.clone());
scalers.push(Box::new(BackoffAwareScaler::new(
ProviderSpreadScaler::new(
snapshot_data.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_owned(),
provider_id,
provider_reference: props.image.to_owned(),
spread_config: SpreadScalerProperty {
instances: 1,
spread: vec![],
},
model_name: name.to_owned(),
provider_config: config_names,
},
&component.name,
),
notifier.to_owned(),
config_scalers,
secret_scalers,
notifier_subject,
name,
// Providers are a bit longer because it can take a bit to download
Some(Duration::from_secs(60)),
)) as BoxedScaler)
}
}
}
}
scalers
}
/// Returns a tuple which is a list of scalers and a list of the names of the configs that the
/// scalers use.
///
/// Any input [ConfigProperty] that has a `properties` field will be converted into a [ConfigScaler], and
/// the name of the configuration will be modified to be unique to the model and component. If the properties
/// field is not present, the name will be used as-is and assumed that it's managed externally to wadm.
fn config_to_scalers<C: ConfigSource + Send + Sync + Clone>(
config_source: C,
model_name: &str,
configs: &[ConfigProperty],
) -> (Vec<ConfigScaler<C>>, Vec<String>) {
configs
.iter()
.map(|config| {
let name = if config.properties.is_some() {
compute_component_id(model_name, None, &config.name)
} else {
config.name.clone()
};
(
ConfigScaler::new(config_source.clone(), &name, config.properties.as_ref()),
name,
)
})
.unzip()
}
fn secrets_to_scalers<S: SecretSource + Send + Sync + Clone>(
secret_source: S,
model_name: &str,
secrets: &[SecretProperty],
policies: &HashMap<&String, &Policy>,
) -> (Vec<SecretScaler<S>>, Vec<String>) {
secrets
.iter()
.map(|s| {
let name = compute_secret_id(model_name, None, &s.name);
let policy = *policies.get(&s.source.policy).unwrap();
(
SecretScaler::new(
name.clone(),
s.source.clone(),
secret_source.clone(),
policy.clone(),
),
name,
)
})
.unzip()
}
/// Based on the name of the model and the optionally provided ID, returns a unique ID for the
/// component that is a sanitized version of the component reference and model name, separated
/// by a dash.
pub(crate) fn compute_component_id(
model_name: &str,
component_id: Option<&String>,
component_name: &str,
) -> String {
if let Some(id) = component_id {
id.to_owned()
} else {
format!(
"{}-{}",
model_name
.to_lowercase()
.replace(|c: char| !c.is_ascii_alphanumeric(), "_"),
component_name
.to_lowercase()
.replace(|c: char| !c.is_ascii_alphanumeric(), "_")
)
}
}
pub(crate) fn compute_secret_id(
model_name: &str,
component_id: Option<&String>,
component_name: &str,
) -> String {
let name = compute_component_id(model_name, component_id, component_name);
format!("{}_{}", SECRET_CONFIG_PREFIX, name)
}
#[cfg(test)]
mod test {
use crate::scaler::manager::compute_component_id;
#[test]
fn compute_proper_component_id() {
// User supplied ID always takes precedence
assert_eq!(
compute_component_id("mymodel", Some(&"myid".to_string()), "echo"),
"myid"
);
assert_eq!(
compute_component_id(
"some model name with spaces cause yaml",
Some(&"myid".to_string()),
" echo "
),
"myid"
);
// Sanitize component reference
assert_eq!(
compute_component_id("mymodel", None, "echo-component"),
"mymodel-echo_component"
);
// Ensure we can support spaces in the model name, because YAML strings
assert_eq!(
compute_component_id("some model name with spaces cause yaml", None, "echo"),
"some_model_name_with_spaces_cause_yaml-echo"
);
// Ensure we can support spaces in the model name, because YAML strings
// Ensure we can support lowercasing the reference as well, just in case
assert_eq!(
compute_component_id("My ThInG", None, "thing.wasm"),
"my_thing-thing_wasm"
);
}
}

View File

@ -12,16 +12,18 @@ use wadm_types::{api::StatusInfo, TraitProperty};
use crate::{
commands::Command,
events::{Event, ProviderStartFailed, ProviderStarted},
events::{ComponentScaleFailed, ComponentScaled, Event, ProviderStartFailed, ProviderStarted},
publisher::Publisher,
workers::{get_commands_and_result, ConfigSource, SecretSource},
};
pub mod configscaler;
mod convert;
pub mod daemonscaler;
pub mod manager;
pub mod secretscaler;
pub mod spreadscaler;
pub mod statusscaler;
use manager::Notifications;
@ -29,6 +31,7 @@ use self::configscaler::ConfigScaler;
use self::secretscaler::SecretScaler;
const DEFAULT_WAIT_TIMEOUT: Duration = Duration::from_secs(30);
const DEFAULT_SCALER_KIND: &str = "Scaler";
/// A trait describing a struct that can be configured to compute the difference between
/// desired state and configured state, returning a set of commands to approach desired state.
@ -43,11 +46,23 @@ const DEFAULT_WAIT_TIMEOUT: Duration = Duration::from_secs(30);
#[async_trait]
pub trait Scaler {
/// A unique identifier for this scaler type. This is used for logging and for selecting
/// specific scalers as needed. Generally this should be something like
/// `$NAME_OF_SCALER_TYPE-$MODEL_NAME-$OCI_REF`. However, the only requirement is that it can
/// uniquely identify a scaler
/// specific scalers as needed. wadm scalers implement this by computing a sha256 hash of
/// all of the parameters that are used to construct the scaler, therefore ensuring that
/// the ID is unique for each scaler
fn id(&self) -> &str;
/// An optional human-friendly name for this scaler. This is used for logging and for selecting
/// specific scalers as needed. This is optional and by default returns the same value as `id`,
/// and does not have to be unique
fn name(&self) -> String {
self.id().to_string()
}
/// An optional kind of scaler. This is used for logging and for selecting specific scalers as needed
fn kind(&self) -> &str {
DEFAULT_SCALER_KIND
}
/// Determine the status of this scaler according to reconciliation logic. This is the opportunity
/// for scalers to indicate that they are unhealthy with a message as to what's missing.
async fn status(&self) -> StatusInfo;
@ -73,19 +88,21 @@ pub trait Scaler {
async fn cleanup(&self) -> Result<Vec<Command>>;
}
/// The BackoffAwareScaler is a wrapper around a scaler that is responsible for
/// ensuring that a particular [Scaler] has the proper prerequisites in place
/// and should be able to reconcile and issue commands.
/// The BackoffWrapper is a wrapper around a scaler that is responsible for
/// ensuring that a particular scaler doesn't get overwhelmed with events and has the
/// necessary prerequisites to reconcile.
///
/// 1. With the introduction of configuration for wadm applications, the most necessary
/// prerequisite for components, providers and links to start is that their
/// configuration is available. Scalers will not be able to issue commands until
/// the configuration exists.
/// 2. For scalers that issue commands that take a long time to complete, like downloading
/// an image for a provider, the BackoffAwareScaler will ensure that the scaler is not
/// overwhelmed with events and will back off until the expected events have been received.
/// 3. In the future (#253) this wrapper should also be responsible for exponential backoff
/// when a scaler is repeatedly issuing the same commands to prevent thrashing.
/// 1. `required_config` & `required_secrets`: With the introduction of configuration
/// for wadm applications, the most necessary prerequisite for components, providers
/// and links to start is that their configuration is available. Scalers will not be
/// able to issue commands until the configuration exists.
/// 2. `expected_events`: For scalers that issue commands that should result in events,
/// the BackoffWrapper is responsible for ensuring that the scaler doesn't continually
/// issue commands that it's already expecting events for. Commonly this will allow a host
/// to download larger images from an OCI repository without being bombarded with repeat requests.
/// 3. `backoff_status`: If a scaler receives an event that it was expecting, but it was a failure
/// event, the scaler should back off exponentially while reporting that failure status. This both
/// allows for diagnosing issues with reconciliation and prevents thrashing.
///
/// All of the above effectively allows the inner Scaler to only worry about the logic around
/// reconciling and handling events, rather than be concerned about whether or not
@ -94,7 +111,7 @@ pub trait Scaler {
/// The `notifier` is used to publish notifications to add, remove, or recompute
/// expected events with scalers on other wadm instances, as only one wadm instance
/// at a time will handle a specific event.
pub(crate) struct BackoffAwareScaler<T, P, C> {
pub(crate) struct BackoffWrapper<T, P, C> {
scaler: T,
notifier: P,
notify_subject: String,
@ -108,15 +125,21 @@ pub(crate) struct BackoffAwareScaler<T, P, C> {
event_cleaner: Mutex<Option<JoinHandle<()>>>,
/// The amount of time to wait before cleaning up the expected events list
cleanup_timeout: std::time::Duration,
/// The status of the scaler, set when the scaler is backing off due to a
/// failure event.
backoff_status: Arc<RwLock<Option<StatusInfo>>>,
// TODO(#253): Figure out where/when/how to store the backoff and exponentially repeat it
/// Responsible for cleaning up the backoff status after a specified duration
status_cleaner: Mutex<Option<JoinHandle<()>>>,
}
impl<T, P, C> BackoffAwareScaler<T, P, C>
impl<T, P, C> BackoffWrapper<T, P, C>
where
T: Scaler + Send + Sync,
P: Publisher + Send + Sync + 'static,
C: ConfigSource + SecretSource + Send + Sync + Clone + 'static,
{
/// Wraps the given scaler in a new backoff aware scaler. `cleanup_timeout` can be set to a
/// Wraps the given scaler in a new BackoffWrapper. `cleanup_timeout` can be set to a
/// desired waiting time, otherwise it will default to 30s
pub fn new(
scaler: T,
@ -137,6 +160,8 @@ where
expected_events: Arc::new(RwLock::new(Vec::new())),
event_cleaner: Mutex::new(None),
cleanup_timeout: cleanup_timeout.unwrap_or(DEFAULT_WAIT_TIMEOUT),
backoff_status: Arc::new(RwLock::new(None)),
status_cleaner: Mutex::new(None),
}
}
@ -158,25 +183,32 @@ where
expected_events.clear();
}
expected_events.extend(events);
self.set_timed_cleanup().await;
self.set_timed_event_cleanup().await;
}
/// Removes an event pair from the expected events list if one matches the given event
/// Returns true if the event was removed, false otherwise
async fn remove_event(&self, event: &Event) -> Result<bool> {
/// Returns a tuple of bools, the first indicating if the event was removed, and the second
/// indicating if the event was the failure event
async fn remove_event(&self, event: &Event) -> Result<(bool, bool)> {
let mut expected_events = self.expected_events.write().await;
let before_count = expected_events.len();
let mut failed_event = false;
expected_events.retain(|(success, fail)| {
// Retain the event if it doesn't match either the success or optional failure event.
// Most events have a possibility of seeing a failure and either one means we saw the
// event we were expecting
!evt_matches_expected(success, event)
&& !fail
.as_ref()
.map(|f| evt_matches_expected(f, event))
.unwrap_or(false)
let matches_success = evt_matches_expected(success, event);
let matches_failure = fail
.as_ref()
.map_or(false, |f| evt_matches_expected(f, event));
// Update failed_event if the event matches the failure event
failed_event |= matches_failure;
// Retain the event if it doesn't match either the success or failure event
!(matches_success || matches_failure)
});
Ok(expected_events.len() != before_count)
Ok((expected_events.len() < before_count, failed_event))
}
/// Handles an incoming event for the given scaler.
@ -198,8 +230,24 @@ where
#[instrument(level = "trace", skip_all, fields(scaler_id = %self.id()))]
async fn handle_event_internal(&self, event: &Event) -> anyhow::Result<Vec<Command>> {
let model_name = &self.model_name;
let commands: Vec<Command> = if self.remove_event(event).await? {
trace!("Scaler received event that it was expecting");
let (expected_event, failed_event) = self.remove_event(event).await?;
let commands: Vec<Command> = if expected_event {
// So here, if we receive a failed event that it was "expecting"
// Then we know that the scaler status is essentially failed and should retry
// So we should tell the other scalers to remove the event, AND other scalers
// in the process of removing that event will know that it failed.
trace!(failed_event, "Scaler received event that it was expecting");
if failed_event {
let failed_message = match event {
Event::ProviderStartFailed(evt) => evt.error.clone(),
Event::ComponentScaleFailed(evt) => evt.error.clone(),
_ => format!("Received a failed event of type '{}'", event.raw_type()),
};
*self.backoff_status.write().await = Some(StatusInfo::failed(&failed_message));
// TODO(#253): Here we could refer to a stored previous duration and increase it
self.set_timed_status_cleanup(std::time::Duration::from_secs(5))
.await;
}
let data = serde_json::to_vec(&Notifications::RemoveExpectedEvent {
name: model_name.to_owned(),
scaler_id: self.scaler.id().to_owned(),
@ -216,9 +264,12 @@ where
// If a scaler is expecting events still, don't have it handle events. This is effectively
// the backoff mechanism within wadm
Vec::with_capacity(0)
} else if self.backoff_status.read().await.is_some() {
trace!("Scaler received event but is in backoff, ignoring");
Vec::with_capacity(0)
} else {
trace!("Scaler is not backing off, checking configuration");
let (mut commands, res) = get_commands_and_result(
let (mut config_commands, res) = get_commands_and_result(
self.required_config
.iter()
.map(|config| async { config.handle_event(event).await }),
@ -248,9 +299,10 @@ where
);
}
if !commands.is_empty() && !secret_commands.is_empty() {
commands.append(&mut secret_commands);
return Ok(commands);
// If the config scalers or secret scalers have commands to send, return them
if !config_commands.is_empty() || !secret_commands.is_empty() {
config_commands.append(&mut secret_commands);
return Ok(config_commands);
}
trace!("Scaler required configuration is present, handling event");
@ -258,9 +310,7 @@ where
// Based on the commands, compute the events that we expect to see for this scaler. The scaler
// will then ignore incoming events until all of the expected events have been received.
let expected_events = commands
.iter()
.filter_map(|cmd| cmd.corresponding_event(model_name));
let expected_events = commands.iter().filter_map(|cmd| cmd.corresponding_event());
self.add_events(expected_events, false).await;
@ -288,7 +338,11 @@ where
// If we're already in backoff, return an empty list
let current_event_count = self.event_count().await;
if current_event_count > 0 {
trace!(%current_event_count, "Scaler is backing off, not reconciling");
trace!(%current_event_count, "Scaler is awaiting an event, not reconciling");
return Ok(Vec::with_capacity(0));
}
if self.backoff_status.read().await.is_some() {
tracing::info!(%current_event_count, "Scaler is backing off, not reconciling");
return Ok(Vec::with_capacity(0));
}
@ -318,7 +372,7 @@ where
self.add_events(
commands
.iter()
.filter_map(|command| command.corresponding_event(&self.model_name)),
.filter_map(|command| command.corresponding_event()),
true,
)
.await;
@ -374,7 +428,7 @@ where
}
/// Sets a timed cleanup task to clear the expected events list after a timeout
async fn set_timed_cleanup(&self) {
async fn set_timed_event_cleanup(&self) {
let mut event_cleaner = self.event_cleaner.lock().await;
// Clear any existing handle
if let Some(handle) = event_cleaner.take() {
@ -392,12 +446,30 @@ where
.instrument(tracing::trace_span!("event_cleaner", scaler_id = %self.id())),
));
}
/// Sets a timed cleanup task to clear the expected events list after a timeout
async fn set_timed_status_cleanup(&self, timeout: Duration) {
let mut status_cleaner = self.status_cleaner.lock().await;
// Clear any existing handle
if let Some(handle) = status_cleaner.take() {
handle.abort();
}
let backoff_status = self.backoff_status.clone();
*status_cleaner = Some(tokio::spawn(
async move {
tokio::time::sleep(timeout).await;
trace!("Reached status cleanup timeout, clearing backoff status");
backoff_status.write().await.take();
}
.instrument(tracing::trace_span!("status_cleaner", scaler_id = %self.id())),
));
}
}
#[async_trait]
/// The [Scaler](Scaler) trait implementation for the [BackoffAwareScaler](BackoffAwareScaler)
/// is mostly a simple wrapper, with two exceptions, which allow scalers to sync expected
/// events between different wadm instances.
/// The [`Scaler`] trait implementation for the [`BackoffWrapper`] is mostly a simple wrapper,
/// with three exceptions, which allow scalers to sync state between different wadm instances.
///
/// * `handle_event` calls an internal method that uses a notifier to publish notifications to
/// all Scalers, even running on different wadm instances, to handle that event. The resulting
@ -405,7 +477,9 @@ where
/// * `reconcile` calls an internal method that uses a notifier to ensure all Scalers, even
/// running on different wadm instances, compute their expected events in response to the
/// reconciliation commands in order to "back off".
impl<T, P, C> Scaler for BackoffAwareScaler<T, P, C>
/// * `status` will first check to see if the scaler is in a backing off state, and if so, return
/// the backoff status. Otherwise, it will return the status of the scaler.
impl<T, P, C> Scaler for BackoffWrapper<T, P, C>
where
T: Scaler + Send + Sync,
P: Publisher + Send + Sync + 'static,
@ -416,8 +490,22 @@ where
self.scaler.id()
}
fn kind(&self) -> &str {
// Pass through the kind of the wrapped scaler
self.scaler.kind()
}
fn name(&self) -> String {
self.scaler.name()
}
async fn status(&self) -> StatusInfo {
self.scaler.status().await
// If the scaler has a backoff status, return that, otherwise return the status of the scaler
if let Some(status) = self.backoff_status.read().await.clone() {
status
} else {
self.scaler.status().await
}
}
async fn update_config(&mut self, config: TraitProperty) -> Result<Vec<Command>> {
@ -465,15 +553,49 @@ fn evt_matches_expected(incoming: &Event, expected: &Event) -> bool {
(
Event::ProviderStartFailed(ProviderStartFailed {
provider_id: p1,
provider_ref: i1,
host_id: h1,
..
}),
Event::ProviderStartFailed(ProviderStartFailed {
provider_id: p2,
provider_ref: i2,
host_id: h2,
..
}),
) => p1 == p2 && h1 == h2,
) => p1 == p2 && h1 == h2 && i1 == i2,
(
Event::ComponentScaled(ComponentScaled {
annotations: a1,
image_ref: i1,
component_id: c1,
host_id: h1,
..
}),
Event::ComponentScaled(ComponentScaled {
annotations: a2,
image_ref: i2,
component_id: c2,
host_id: h2,
..
}),
) => a1 == a2 && i1 == i2 && c1 == c2 && h1 == h2,
(
Event::ComponentScaleFailed(ComponentScaleFailed {
annotations: a1,
image_ref: i1,
component_id: c1,
host_id: h1,
..
}),
Event::ComponentScaleFailed(ComponentScaleFailed {
annotations: a2,
image_ref: i2,
component_id: c2,
host_id: h2,
..
}),
) => a1 == a2 && i1 == i2 && c1 == c2 && h1 == h2,
_ => false,
}
}

View File

@ -1,16 +1,12 @@
use anyhow::{Context, Result};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, HashMap},
hash::{DefaultHasher, Hash, Hasher},
};
use tokio::sync::RwLock;
use tracing::{debug, error, instrument, trace};
use wadm_types::{
api::{StatusInfo, StatusType},
Policy, SecretSourceProperty, TraitProperty,
Policy, SecretProperty, TraitProperty,
};
use wasmcloud_secrets_types::SecretConfig;
use crate::{
commands::{Command, DeleteConfig, PutConfig},
@ -19,43 +15,56 @@ use crate::{
workers::SecretSource,
};
pub const SECRET_CONFIG_PREFIX: &str = "SECRET";
use super::compute_id_sha256;
const SECRET_SCALER_KIND: &str = "SecretScaler";
pub struct SecretScaler<SecretSource> {
secret_source: SecretSource,
id: String,
/// The key to use in the configdata bucket for this secret
secret_name: String,
source: SecretSourceProperty,
secret_config: SecretConfig,
id: String,
status: RwLock<StatusInfo>,
policy: Policy,
}
#[derive(Deserialize, Serialize)]
struct PolicyProperties {
#[serde(rename = "type")]
policy_type: String,
properties: BTreeMap<String, String>,
}
impl<S: SecretSource> SecretScaler<S> {
pub fn new(
secret_name: String,
source: SecretSourceProperty,
secret_source: S,
policy: Policy,
secret_property: SecretProperty,
secret_source: S,
) -> Self {
let mut id = secret_name.clone();
let mut hasher = DefaultHasher::new();
source.hash(&mut hasher);
id.extend(format!("-{}", hasher.finish()).chars());
// Compute the id of this scaler based on all of the values that make it unique.
// This is used during upgrades to determine if a scaler is the same as a previous one.
let mut id_parts = vec![
secret_name.as_str(),
policy.name.as_str(),
policy.policy_type.as_str(),
secret_property.name.as_str(),
secret_property.properties.policy.as_str(),
secret_property.properties.key.as_str(),
];
if let Some(version) = secret_property.properties.version.as_ref() {
id_parts.push(version.as_str());
}
id_parts.extend(
policy
.properties
.iter()
.flat_map(|(k, v)| vec![k.as_str(), v.as_str()]),
);
let id = compute_id_sha256(&id_parts);
let secret_config = config_from_manifest_structures(policy, secret_property)
.expect("failed to create secret config from policy and secret properties");
Self {
id,
secret_name,
source,
secret_config,
secret_source,
status: RwLock::new(StatusInfo::reconciling("")),
policy,
}
}
}
@ -66,6 +75,14 @@ impl<S: SecretSource + Send + Sync + Clone> Scaler for SecretScaler<S> {
&self.id
}
fn kind(&self) -> &str {
SECRET_SCALER_KIND
}
fn name(&self) -> String {
self.secret_config.name.to_string()
}
async fn status(&self) -> StatusInfo {
let _ = self.reconcile().await;
self.status.read().await.to_owned()
@ -102,28 +119,35 @@ impl<S: SecretSource + Send + Sync + Clone> Scaler for SecretScaler<S> {
#[instrument(level = "trace", skip_all, scaler_id = %self.id)]
async fn reconcile(&self) -> Result<Vec<Command>> {
debug!(self.secret_name, "Fetching configuration");
match (
self.secret_source.get_secret(&self.secret_name).await,
self.source.clone(),
) {
match self.secret_source.get_secret(&self.secret_name).await {
// If configuration matches what's supplied, this scaler is deployed
(Ok(Some(config)), scaler_config) if config == scaler_config => {
Ok(Some(config)) if config == self.secret_config => {
*self.status.write().await = StatusInfo::deployed("");
Ok(Vec::new())
}
// If configuration is out of sync, we put the configuration
(Ok(_config), scaler_config) => {
Ok(_config) => {
debug!(self.secret_name, "Putting secret");
let cfg = merge_policy_properties(&self.policy, &scaler_config)?;
match self.secret_config.clone().try_into() {
Ok(config) => {
*self.status.write().await = StatusInfo::reconciling("Secret out of sync");
*self.status.write().await = StatusInfo::reconciling("Secret out of sync");
Ok(vec![Command::PutConfig(PutConfig {
config_name: self.secret_name.clone(),
config: cfg,
})])
Ok(vec![Command::PutConfig(PutConfig {
config_name: self.secret_name.clone(),
config,
})])
}
Err(e) => {
*self.status.write().await = StatusInfo::failed(&format!(
"Failed to convert secret config to map: {}.",
e
));
Ok(vec![])
}
}
}
(Err(e), _) => {
Err(e) => {
error!(error = %e, "SecretScaler failed to fetch configuration");
*self.status.write().await = StatusInfo::failed(&e.to_string());
Ok(Vec::new())
@ -139,30 +163,31 @@ impl<S: SecretSource + Send + Sync + Clone> Scaler for SecretScaler<S> {
}
}
fn merge_policy_properties(
policy: &Policy,
reference: &SecretSourceProperty,
) -> anyhow::Result<HashMap<String, String>> {
let mut cfg: HashMap<String, String> = reference.clone().try_into()?;
let mut properties = PolicyProperties {
policy_type: policy.policy_type.clone(),
properties: policy.properties.clone(),
};
let backend = properties
.properties
/// Merge policy and properties into a [`SecretConfig`] for later use.
fn config_from_manifest_structures(
policy: Policy,
reference: SecretProperty,
) -> anyhow::Result<SecretConfig> {
let mut policy_properties = policy.properties.clone();
let backend = policy_properties
.remove("backend")
.context("policy did not have a backend property")?;
cfg.insert("backend".to_string(), backend);
let policy_json = serde_json::to_string(&properties)?;
cfg.insert("policy_properties".to_string(), policy_json);
Ok(cfg)
Ok(SecretConfig::new(
reference.name.clone(),
backend,
reference.properties.key.clone(),
reference.properties.field.clone(),
reference.properties.version.clone(),
policy_properties
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
))
}
#[cfg(test)]
mod test {
use super::merge_policy_properties;
use super::config_from_manifest_structures;
use crate::{
commands::{Command, PutConfig},
@ -190,18 +215,19 @@ mod test {
let secret = SecretProperty {
name: "test".to_string(),
source: SecretSourceProperty {
properties: SecretSourceProperty {
policy: "nats-kv".to_string(),
key: "test".to_string(),
field: None,
version: None,
},
};
let secret_scaler = super::SecretScaler::new(
secret.name.clone(),
secret.source.clone(),
lattice.clone(),
policy.clone(),
secret.clone(),
lattice.clone(),
);
assert_eq!(
@ -209,18 +235,17 @@ mod test {
StatusType::Reconciling
);
let mut cfg =
merge_policy_properties(&policy, &secret.source).expect("failed to merge policy");
cfg.insert("backend".to_string(), "nats-kv".to_string());
let cfg = config_from_manifest_structures(policy, secret.clone())
.expect("failed to merge policy");
assert_eq!(
secret_scaler
.reconcile()
.await
.expect("recocile did not succeed"),
.expect("reconcile did not succeed"),
vec![Command::PutConfig(PutConfig {
config_name: secret.name.clone(),
config: cfg.clone(),
config: cfg.clone().try_into().expect("should convert to map"),
})],
);
@ -239,7 +264,7 @@ mod test {
.expect("handle_event should succeed"),
vec![Command::PutConfig(PutConfig {
config_name: secret.name.clone(),
config: cfg.clone(),
config: cfg.clone().try_into().expect("should convert to map"),
})]
);
assert_eq!(
@ -280,7 +305,7 @@ mod test {
.expect("handle_event should succeed"),
vec![Command::PutConfig(PutConfig {
config_name: secret.name.clone(),
config: cfg.clone(),
config: cfg.clone().try_into().expect("should convert to map"),
})]
);
assert_eq!(

View File

@ -15,7 +15,7 @@ use crate::{
workers::LinkSource,
};
pub const LINK_SCALER_TYPE: &str = "linkdefscaler";
pub const LINK_SCALER_KIND: &str = "LinkScaler";
/// Config for a LinkSpreadConfig
pub struct LinkScalerConfig {
@ -62,6 +62,20 @@ where
&self.id
}
fn kind(&self) -> &str {
LINK_SCALER_KIND
}
fn name(&self) -> String {
format!(
"{} -({}:{})-> {}",
self.config.source_id,
self.config.wit_namespace,
self.config.wit_package,
self.config.target
)
}
async fn status(&self) -> StatusInfo {
let _ = self.reconcile().await;
self.status.read().await.to_owned()
@ -77,7 +91,7 @@ where
async fn handle_event(&self, event: &Event) -> Result<Vec<Command>> {
match event {
// Trigger linkdef creation if this component starts and belongs to this model
Event::ComponentScaled(evt) if evt.component_id == self.config.source_id => {
Event::ComponentScaled(evt) if evt.component_id == self.config.source_id || evt.component_id == self.config.target => {
self.reconcile().await
}
Event::ProviderHealthCheckPassed(ProviderHealthCheckPassed {
@ -108,9 +122,9 @@ where
self.reconcile().await
}
Event::LinkdefSet(LinkdefSet { linkdef })
if linkdef.source_id == self.config.source_id
&& linkdef.target == self.config.target
&& linkdef.name == self.config.name =>
if linkdef.source_id() == self.config.source_id
&& linkdef.target() == self.config.target
&& linkdef.name() == self.config.name =>
{
*self.status.write().await = StatusInfo::deployed("");
Ok(Vec::new())
@ -127,20 +141,23 @@ where
let (exists, _config_different) = linkdefs
.into_iter()
.find(|linkdef| {
&linkdef.source_id == source_id
&& &linkdef.target == target
&& linkdef.name == self.config.name
linkdef.source_id() == source_id
&& linkdef.target() == target
&& linkdef.name() == self.config.name
})
.map(|linkdef| {
(
true,
// TODO(#88): reverse compare too
// Ensure all named configs are the same
linkdef.source_config.iter().all(|config_name| {
self.config.source_config.iter().any(|c| c == config_name)
}) || linkdef.target_config.iter().all(|config_name| {
self.config.target_config.iter().any(|c| c == config_name)
}),
// Ensure all supplied configs (both source and target) are the same
linkdef
.source_config()
.iter()
.eq(self.config.source_config.iter())
&& linkdef
.target_config()
.iter()
.eq(self.config.target_config.iter()),
)
})
.unwrap_or((false, false));
@ -215,7 +232,7 @@ impl<S: ReadStore + Send + Sync, L: LinkSource> LinkScaler<S, L> {
// that make it unique. This is used during upgrades to determine if a
// scaler is the same as a previous one.
let mut id_parts = vec![
LINK_SCALER_TYPE,
LINK_SCALER_KIND,
&link_config.model_name,
&link_config.name,
&link_config.source_id,
@ -261,7 +278,7 @@ mod test {
vec,
};
use wasmcloud_control_interface::InterfaceLinkDefinition;
use wasmcloud_control_interface::Link;
use chrono::Utc;
@ -413,26 +430,25 @@ mod test {
let provider_ref = "provider_ref".to_string();
let provider_id = "provider".to_string();
let linkdef = InterfaceLinkDefinition {
source_id: component_id.to_string(),
target: provider_id.to_string(),
wit_namespace: "namespace".to_string(),
wit_package: "package".to_string(),
interfaces: vec!["interface".to_string()],
name: "default".to_string(),
source_config: vec![],
target_config: vec![],
};
let linkdef = Link::builder()
.source_id(&component_id)
.target(&provider_id)
.wit_namespace("namespace")
.wit_package("package")
.interfaces(vec!["interface".to_string()])
.name("default")
.build()
.unwrap();
let scaler = LinkScaler::new(
create_store(&lattice_id, &component_ref, &provider_ref).await,
LinkScalerConfig {
source_id: linkdef.source_id.clone(),
target: linkdef.target.clone(),
wit_namespace: linkdef.wit_namespace.clone(),
wit_package: linkdef.wit_package.clone(),
wit_interfaces: linkdef.interfaces.clone(),
name: linkdef.name.clone(),
source_id: linkdef.source_id().to_string(),
target: linkdef.target().to_string(),
wit_namespace: linkdef.wit_namespace().to_string(),
wit_package: linkdef.wit_package().to_string(),
wit_interfaces: linkdef.interfaces().clone(),
name: linkdef.name().to_string(),
source_config: vec![],
target_config: vec![],
lattice_id: lattice_id.clone(),
@ -566,17 +582,15 @@ mod test {
let commands = link_scaler
.handle_event(&Event::LinkdefSet(LinkdefSet {
linkdef: InterfaceLinkDefinition {
linkdef: Link::builder()
// NOTE: contract, link, and provider id matches but the component is different
source_id: "nm0001772".to_string(),
target: "VASDASD".to_string(),
wit_namespace: "wasmcloud".to_string(),
wit_package: "httpserver".to_string(),
interfaces: vec![],
name: "default".to_string(),
source_config: vec![],
target_config: vec![],
},
.source_id("nm0001772")
.target("VASDASD")
.wit_namespace("wasmcloud")
.wit_package("httpserver")
.name("default")
.build()
.unwrap(),
}))
.await
.expect("");

View File

@ -1,5 +1,6 @@
use std::collections::{BTreeMap, HashSet};
use std::{cmp::Ordering, cmp::Reverse, collections::HashMap};
use std::{
cmp::Ordering, cmp::Reverse, collections::BTreeMap, collections::HashMap, collections::HashSet,
};
use anyhow::Result;
use async_trait::async_trait;
@ -9,7 +10,7 @@ use wadm_types::{
api::StatusInfo, Spread, SpreadScalerProperty, TraitProperty, DEFAULT_SPREAD_WEIGHT,
};
use crate::events::HostHeartbeat;
use crate::events::{ConfigSet, HostHeartbeat};
use crate::{
commands::{Command, ScaleComponent},
events::{Event, HostStarted, HostStopped},
@ -26,7 +27,7 @@ pub mod provider;
// Annotation constants
const SPREAD_KEY: &str = "wasmcloud.dev/spread_name";
pub const COMPONENT_SPREAD_SCALER_TYPE: &str = "componentspreadscaler";
pub const SPREAD_SCALER_KIND: &str = "SpreadScaler";
/// Config for a ComponentSpreadScaler
#[derive(Clone)]
@ -64,6 +65,14 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentSpreadScaler<S> {
&self.id
}
fn kind(&self) -> &str {
SPREAD_SCALER_KIND
}
fn name(&self) -> String {
self.spread_config.component_id.to_string()
}
async fn status(&self) -> StatusInfo {
let _ = self.reconcile().await;
self.status.read().await.to_owned()
@ -105,6 +114,9 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentSpreadScaler<S> {
Ok(Vec::new())
}
}
Event::ConfigSet(ConfigSet { config_name }) if self.config.contains(config_name) => {
self.reconcile().await
}
// No other event impacts the job of this scaler so we can ignore it
_ => Ok(Vec::new()),
}
@ -118,8 +130,6 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentSpreadScaler<S> {
.get::<Component>(&self.spread_config.lattice_id, component_id)
.await?;
let mut spread_status = vec![];
let hosts = self
.store
.list::<Host>(&self.spread_config.lattice_id)
@ -163,7 +173,9 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentSpreadScaler<S> {
return Ok(remove_ineligible);
}
let mut spread_status = vec![];
trace!(spread_requirements = ?self.spread_requirements, ?component_id, "Computing commands");
let mut component_instances_per_eligible_host: HashMap<&String, usize> = HashMap::new();
let commands = self
.spread_requirements
.iter()
@ -200,6 +212,14 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentSpreadScaler<S> {
}).collect()
})
.unwrap_or_default();
running_components_per_host.iter().for_each(|(host_id, count)| {
component_instances_per_eligible_host
.entry(host_id)
.and_modify(|e| *e += count)
.or_insert(*count);
});
let current_count: usize = running_components_per_host.values().sum();
trace!(current = %current_count, expected = %count, "Calculated running components, reconciling with expected count");
// Here we'll generate commands for the proper host depending on where they are running
@ -259,17 +279,36 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ComponentSpreadScaler<S> {
.collect::<Vec<Command>>();
trace!(?commands, "Calculated commands for component scaler");
let status = if spread_status.is_empty() {
StatusInfo::deployed("")
} else {
StatusInfo::failed(
// Detect spread requirement conflicts
if let Some(message) = detect_spread_requirement_conflicts(
&self.spread_requirements,
&hosts,
&component_instances_per_eligible_host,
&commands,
) {
let status = StatusInfo::failed(&message);
trace!(?status, "Updating scaler status");
*self.status.write().await = status;
return Ok(vec![]);
}
let status = match (spread_status.is_empty(), commands.is_empty()) {
// No failures, no commands, scaler satisfied
(true, true) => StatusInfo::deployed(""),
// No failures, commands generated, scaler is reconciling
(true, false) => {
StatusInfo::reconciling(&format!("Scaling component on {} host(s)", commands.len()))
}
// Failures occurred, scaler is in a failed state
(false, _) => StatusInfo::failed(
&spread_status
.into_iter()
.map(|s| s.message)
.collect::<Vec<String>>()
.join(" "),
)
),
};
trace!(?status, "Updating scaler status");
*self.status.write().await = status;
@ -312,7 +351,7 @@ impl<S: ReadStore + Send + Sync> ComponentSpreadScaler<S> {
// that make it unique. This is used during upgrades to determine if a
// scaler is the same as a previous one.
let mut id_parts = vec![
COMPONENT_SPREAD_SCALER_TYPE,
SPREAD_SCALER_KIND,
&model_name,
component_name,
&component_id,
@ -457,6 +496,94 @@ fn compute_spread(spread_config: &SpreadScalerProperty) -> Vec<(Spread, usize)>
computed_spreads
}
fn detect_spread_requirement_conflicts(
spread_requirements: &[(Spread, usize)],
hosts: &HashMap<String, Host>,
running_instances_per_host: &HashMap<&String, usize>,
commands: &[Command],
) -> Option<String> {
// Step 1: Determine the union of all eligible hosts for the configured spreads
// and collect the current instance count for each eligible host
let mut eligible_hosts_instances: HashMap<String, usize> = HashMap::new();
for (spread, _) in spread_requirements {
for (host_id, host) in hosts {
if spread.requirements.iter().all(|(key, value)| {
host.labels
.get(key)
.map(|val| val == value)
.unwrap_or(false)
}) {
let count = running_instances_per_host
.get(host_id)
.cloned()
.unwrap_or(0);
eligible_hosts_instances.insert(host_id.clone(), count);
}
}
}
// Step 2: derive changeset from commands (for commands that share the same host_id, select the command with the highest instance count & idx is used as a tiebreaker)
let mut changeset: HashMap<String, (usize, usize)> = HashMap::new();
for (idx, command) in commands.iter().enumerate() {
if let Command::ScaleComponent(ScaleComponent { host_id, count, .. }) = command {
let entry = changeset.entry(host_id.clone()).or_insert((0, usize::MAX));
if *count as usize > entry.0 || (*count as usize == entry.0 && idx < entry.1) {
*entry = (*count as usize, idx);
}
}
}
// Apply changeset to the eligible_hosts_instances
for (host_id, (count, _)) in changeset {
if let Some(current_count) = eligible_hosts_instances.get_mut(&host_id) {
*current_count = count;
}
}
// Step 3: Create a structure that maps a Spread to a tuple
// (spread_eligible_hosts_total_instance_count_if_all_commands_are_applied, target_instance_count_based_on_spread_weight)
let mut spread_instances: HashMap<String, (usize, usize)> = HashMap::new();
for (spread, target_count) in spread_requirements {
let projected_count: usize = eligible_hosts_instances
.iter()
.filter_map(|(host_id, count)| {
if spread.requirements.iter().all(|(key, value)| {
hosts
.get(host_id)
.unwrap()
.labels
.get(key)
.map(|val| val == value)
.unwrap_or(false)
}) {
Some(count)
} else {
None
}
})
.sum();
spread_instances.insert(spread.name.clone(), (projected_count, *target_count));
}
// Step 4: Compare the tuples' values to detect conflicts
let mut conflicts = Vec::new();
for (spread_name, (projected_count, target_count)) in spread_instances {
if projected_count != target_count {
conflicts.push(format!(
"Spread requirement conflict: {} spread requires {} instances vs {} computed from reconciliation commands",
spread_name, target_count, projected_count
));
}
}
if conflicts.is_empty() {
return None;
}
Some(conflicts.join(", "))
}
#[cfg(test)]
mod test {
use super::*;
@ -468,8 +595,8 @@ mod test {
use anyhow::Result;
use chrono::Utc;
use wadm_types::{Spread, SpreadScalerProperty};
use wasmcloud_control_interface::InterfaceLinkDefinition;
use wadm_types::{api::StatusType, Spread, SpreadScalerProperty};
use wasmcloud_control_interface::Link;
use crate::{
commands::Command,
@ -641,59 +768,78 @@ mod test {
#[tokio::test]
async fn can_compute_spread_commands() -> Result<()> {
let lattice_id = "hoohah_multi_stop_component";
let lattice_id = "can_compute_spread_commands";
let component_reference = "fakecloud.azurecr.io/echo:0.3.4".to_string();
let component_id = "fakecloud_azurecr_io_echo_0_3_4".to_string();
let host_id = "NASDASDIMAREALHOST";
let host_id1 = "HOST_ONE";
let host_id2 = "HOST_TWO";
let host_id3 = "HOST_THREE";
let store = Arc::new(TestStore::default());
// STATE SETUP BEGIN, ONE HOST
store
.store(
lattice_id,
host_id.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::new(),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id.to_string(),
last_seen: Utc::now(),
},
)
.await?;
// Create three hosts with different labels
let mut host1_labels = HashMap::new();
host1_labels.insert("zone".to_string(), "east".to_string());
// Ensure we compute if a weights aren't specified
let complex_spread = SpreadScalerProperty {
let mut host2_labels = HashMap::new();
host2_labels.insert("zone".to_string(), "west".to_string());
let mut host3_labels = HashMap::new();
host3_labels.insert("zone".to_string(), "central".to_string());
// Store the hosts
for (host_id, labels) in [
(host_id1, host1_labels),
(host_id2, host2_labels),
(host_id3, host3_labels),
] {
store
.store(
lattice_id,
host_id.to_string(),
Host {
components: HashMap::new(),
friendly_name: format!("host_{}", host_id),
labels,
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id.to_string(),
last_seen: Utc::now(),
},
)
.await?;
}
// Create spread requirements that map to specific hosts
let mut east_requirement = BTreeMap::new();
east_requirement.insert("zone".to_string(), "east".to_string());
let mut west_requirement = BTreeMap::new();
west_requirement.insert("zone".to_string(), "west".to_string());
let mut central_requirement = BTreeMap::new();
central_requirement.insert("zone".to_string(), "central".to_string());
let spread_config = SpreadScalerProperty {
instances: 103,
spread: vec![
Spread {
// 9 + 1 (remainder trip)
name: "ComplexOne".to_string(),
requirements: BTreeMap::new(),
name: "EastZone".to_string(),
requirements: east_requirement, // Maps to host1
weight: Some(42),
},
Spread {
// 0
name: "ComplexTwo".to_string(),
requirements: BTreeMap::new(),
name: "WestZone".to_string(),
requirements: west_requirement, // Maps to host2
weight: Some(3),
},
Spread {
// 8
name: "ComplexThree".to_string(),
requirements: BTreeMap::new(),
name: "CentralZone".to_string(),
requirements: central_requirement, // Maps to host3
weight: Some(37),
},
Spread {
// 84 + 1 (remainder trip)
name: "ComplexFour".to_string(),
requirements: BTreeMap::new(),
weight: Some(384),
},
],
};
@ -703,38 +849,46 @@ mod test {
component_id.to_string(),
lattice_id.to_string(),
MODEL_NAME.to_string(),
complex_spread,
spread_config,
"fake_component",
vec![],
);
let cmds = spreadscaler.reconcile().await?;
assert_eq!(cmds.len(), 3);
// With weights 42:3:37 and total instances of 103
// EastZone (east) should get (52 + 1) instances
// WestZone (west) should get 3 instances
// CentralZone (central) should get (46 + 1) instances
assert!(cmds.contains(&Command::ScaleComponent(ScaleComponent {
component_id: component_id.to_string(),
reference: component_reference.to_string(),
host_id: host_id.to_string(),
count: 10,
host_id: host_id1.to_string(), // east zone
count: 53,
model_name: MODEL_NAME.to_string(),
annotations: spreadscaler_annotations("ComplexOne", spreadscaler.id()),
annotations: spreadscaler_annotations("EastZone", spreadscaler.id()),
config: vec![]
})));
assert!(cmds.contains(&Command::ScaleComponent(ScaleComponent {
component_id: component_id.to_string(),
reference: component_reference.to_string(),
host_id: host_id.to_string(),
count: 8,
host_id: host_id2.to_string(), // west zone
count: 3,
model_name: MODEL_NAME.to_string(),
annotations: spreadscaler_annotations("ComplexThree", spreadscaler.id()),
annotations: spreadscaler_annotations("WestZone", spreadscaler.id()),
config: vec![]
})));
assert!(cmds.contains(&Command::ScaleComponent(ScaleComponent {
component_id: component_id.to_string(),
reference: component_reference.to_string(),
host_id: host_id.to_string(),
count: 85,
host_id: host_id3.to_string(), // central zone
count: 47,
model_name: MODEL_NAME.to_string(),
annotations: spreadscaler_annotations("ComplexFour", spreadscaler.id()),
annotations: spreadscaler_annotations("CentralZone", spreadscaler.id()),
config: vec![]
})));
@ -1039,114 +1193,6 @@ mod test {
Ok(())
}
#[tokio::test]
async fn can_handle_multiple_spread_matches() -> Result<()> {
let lattice_id = "multiple_spread_matches";
let component_reference = "fakecloud.azurecr.io/echo:0.3.4".to_string();
let component_id = "fakecloud_azurecr_io_echo_0_3_4".to_string();
let host_id = "NASDASDIMAREALHOST";
let store = Arc::new(TestStore::default());
// Run 75% in east, 25% on resilient hosts
let real_spread = SpreadScalerProperty {
instances: 20,
spread: vec![
Spread {
name: "SimpleOne".to_string(),
requirements: BTreeMap::from_iter([("region".to_string(), "east".to_string())]),
weight: Some(75),
},
Spread {
name: "SimpleTwo".to_string(),
requirements: BTreeMap::from_iter([(
"resilient".to_string(),
"true".to_string(),
)]),
weight: Some(25),
},
],
};
let spreadscaler = ComponentSpreadScaler::new(
store.clone(),
component_reference.to_string(),
component_id.to_string(),
lattice_id.to_string(),
MODEL_NAME.to_string(),
real_spread,
"fake_component",
vec![],
);
// STATE SETUP BEGIN, ONE HOST
store
.store(
lattice_id,
host_id.to_string(),
Host {
components: HashMap::from_iter([(component_id.to_string(), 10)]),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "east".to_string()),
("resilient".to_string(), "true".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
component_id.to_string(),
Component {
id: component_id.to_string(),
name: "Faketor".to_string(),
issuer: "AASDASDASDASD".to_string(),
instances: HashMap::from_iter([(
host_id.to_string(),
// 10 instances on this host under the first spread
HashSet::from_iter([WadmComponentInfo {
count: 10,
annotations: spreadscaler_annotations("SimpleOne", spreadscaler.id()),
}]),
)]),
reference: component_reference.to_string(),
},
)
.await?;
let cmds = spreadscaler.reconcile().await?;
assert_eq!(cmds.len(), 2);
// Should be enforcing 10 instances per spread
assert!(cmds.contains(&Command::ScaleComponent(ScaleComponent {
component_id: "fakecloud_azurecr_io_echo_0_3_4".to_string(),
reference: component_reference.to_string(),
host_id: host_id.to_string(),
count: 15,
model_name: MODEL_NAME.to_string(),
annotations: spreadscaler_annotations("SimpleOne", spreadscaler.id()),
config: vec![]
})));
assert!(cmds.contains(&Command::ScaleComponent(ScaleComponent {
component_id: "fakecloud_azurecr_io_echo_0_3_4".to_string(),
reference: component_reference.to_string(),
host_id: host_id.to_string(),
count: 5,
model_name: MODEL_NAME.to_string(),
annotations: spreadscaler_annotations("SimpleTwo", spreadscaler.id()),
config: vec![]
})));
Ok(())
}
#[tokio::test]
async fn calculates_proper_scale_commands() -> Result<()> {
let lattice_id = "calculates_proper_scale_commands";
@ -1486,7 +1532,7 @@ mod test {
.is_empty());
assert!(blobby_spreadscaler
.handle_event(&Event::LinkdefSet(LinkdefSet {
linkdef: InterfaceLinkDefinition::default()
linkdef: Link::default()
}))
.await?
.is_empty());
@ -1663,4 +1709,358 @@ mod test {
.iter()
.any(|(id, _host)| *id == "NASDASDIMAREALHOST4"));
}
#[tokio::test]
async fn can_detect_spread_requirement_conflicts_1() -> Result<()> {
let lattice_id = "spread_requirement_conflicts";
let component_reference = "fakecloud.azurecr.io/echo:0.1.0".to_string();
let component_id = "fakecloud_azurecr_io_echo_0_1_0".to_string();
let component_name = "fakecomponent".to_string();
let store = Arc::new(TestStore::default());
let host_id_1 = "NASDASDIMAREALHOST1";
let host_id_2 = "NASDASDIMAREALHOST2";
let host_id_3 = "NASDASDIMAREALHOST3";
let host_id_4 = "NASDASDIMAREALHOST4";
// Create hosts with the specified labels and add them to the store
store
.store(
lattice_id,
host_id_1.to_string(),
Host {
components: HashMap::new(),
friendly_name: "node1".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "us-east-1".to_string()),
("cloud".to_string(), "real".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id_1.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
host_id_2.to_string(),
Host {
components: HashMap::new(),
friendly_name: "node2".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "us-east-2".to_string()),
("cloud".to_string(), "real".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id_2.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
host_id_3.to_string(),
Host {
components: HashMap::new(),
friendly_name: "node3".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "us-west-1".to_string()),
("cloud".to_string(), "real".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id_3.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
host_id_4.to_string(),
Host {
components: HashMap::new(),
friendly_name: "node4".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "us-west-2".to_string()),
("cloud".to_string(), "real".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id_4.to_string(),
last_seen: Utc::now(),
},
)
.await?;
let spread_property = SpreadScalerProperty {
instances: 12,
spread: vec![
Spread {
name: "eastcoast".to_string(),
requirements: BTreeMap::from([("region".to_string(), "us-east-1".to_string())]),
weight: Some(25),
},
Spread {
name: "westcoast".to_string(),
requirements: BTreeMap::from([("region".to_string(), "us-west-1".to_string())]),
weight: Some(25),
},
Spread {
name: "realcloud".to_string(),
requirements: BTreeMap::from([("cloud".to_string(), "real".to_string())]),
weight: Some(50),
},
],
};
let spreadscaler = ComponentSpreadScaler::new(
store.clone(),
component_reference.to_string(),
component_id.to_string(),
lattice_id.to_string(),
MODEL_NAME.to_string(),
spread_property,
&component_name,
vec![],
);
spreadscaler.reconcile().await?;
// Check the status after reconciliation
let status = spreadscaler.status().await;
assert_eq!(status.status_type, StatusType::Failed,);
println!("{:?}", status);
assert!(status.message.contains(&format!(
"Spread requirement conflict: {} spread requires {} instances",
"realcloud", 6
)));
Ok(())
}
#[tokio::test]
async fn can_detect_spread_requirement_conflicts_2() -> Result<()> {
let lattice_id = "spread_requirement_conflicts";
let component_reference = "fakecloud.azurecr.io/echo:0.1.0";
let component_id = "fakecloud_azurecr_io_echo_0_1_0";
let component_name = "fakecomponent";
let store = Arc::new(TestStore::default());
let host_id_1 = "NASDASDIMAREALHOST1";
let host_id_2 = "NASDASDIMAREALHOST2";
let host_id_3 = "NASDASDIMAREALHOST3";
let host_id_4 = "NASDASDIMAREALHOST4";
let spread_property = SpreadScalerProperty {
instances: 12,
spread: vec![
Spread {
name: "eastcoast".to_string(),
requirements: BTreeMap::from([("region".to_string(), "us-east-1".to_string())]),
weight: Some(25),
},
Spread {
name: "westcoast".to_string(),
requirements: BTreeMap::from([("region".to_string(), "us-west-1".to_string())]),
weight: Some(25),
},
Spread {
name: "realcloud".to_string(),
requirements: BTreeMap::from([("cloud".to_string(), "real".to_string())]),
weight: Some(50),
},
],
};
let spreadscaler = ComponentSpreadScaler::new(
store.clone(),
component_reference.to_string(),
component_id.to_string(),
lattice_id.to_string(),
MODEL_NAME.to_string(),
spread_property,
component_name,
vec![],
);
// Create components with the specified labels and add them to the store
store
.store(
lattice_id,
component_id.to_string(),
Component {
id: component_id.to_string(),
name: component_name.to_string(),
issuer: "AASDASDASDASD".to_string(),
instances: HashMap::from_iter([
(
host_id_1.to_string(),
// 1 (realcloud) + 11 (eastcoast) = 12 instances on this host
HashSet::from_iter([
WadmComponentInfo {
count: 1,
annotations: spreadscaler_annotations(
"realcloud",
spreadscaler.id(),
),
},
WadmComponentInfo {
count: 11,
annotations: spreadscaler_annotations(
"eastcoast",
spreadscaler.id(),
),
},
]),
),
(
host_id_2.to_string(),
// 0 instances on this host
HashSet::from_iter([]),
),
(
host_id_3.to_string(),
// 2 (realcloud) + 33 (west) = 35 instances on this host
HashSet::from_iter([
WadmComponentInfo {
count: 2,
annotations: spreadscaler_annotations(
"realcloud",
spreadscaler.id(),
),
},
WadmComponentInfo {
count: 33,
annotations: spreadscaler_annotations(
"westcoast",
spreadscaler.id(),
),
},
]),
),
(
host_id_4.to_string(),
// 44 (realcloud) instances on this host
HashSet::from_iter([WadmComponentInfo {
count: 44,
annotations: spreadscaler_annotations(
"realcloud",
spreadscaler.id(),
),
}]),
),
]),
reference: component_reference.to_string(),
},
)
.await?;
// Create hosts with the specified labels and add them to the store
store
.store(
lattice_id,
host_id_1.to_string(),
Host {
components: HashMap::from_iter([(component_id.to_string(), 12)]),
friendly_name: "node1".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "us-east-1".to_string()),
("cloud".to_string(), "real".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id_1.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
host_id_2.to_string(),
Host {
components: HashMap::from_iter([(component_id.to_string(), 0)]),
friendly_name: "node2".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "us-east-2".to_string()),
("cloud".to_string(), "real".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id_2.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
host_id_3.to_string(),
Host {
components: HashMap::from_iter([(component_id.to_string(), 35)]),
friendly_name: "node3".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "us-west-1".to_string()),
("cloud".to_string(), "real".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id_3.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
host_id_4.to_string(),
Host {
components: HashMap::from_iter([(component_id.to_string(), 44)]),
friendly_name: "node4".to_string(),
labels: HashMap::from_iter([
("region".to_string(), "us-west-2".to_string()),
("cloud".to_string(), "real".to_string()),
]),
providers: HashSet::new(),
uptime_seconds: 123,
version: None,
id: host_id_4.to_string(),
last_seen: Utc::now(),
},
)
.await?;
spreadscaler.reconcile().await?;
// Check the status after reconciliation
let status = spreadscaler.status().await;
assert_eq!(status.status_type, StatusType::Failed,);
println!("{:?}", status);
assert!(status.message.contains(&format!(
"Spread requirement conflict: {} spread requires {} instances",
"realcloud", 6
)));
Ok(())
}
}

View File

@ -7,12 +7,16 @@ use anyhow::Result;
use async_trait::async_trait;
use tokio::sync::{OnceCell, RwLock};
use tracing::{instrument, trace};
use wadm_types::{api::StatusInfo, Spread, SpreadScalerProperty, TraitProperty};
use wadm_types::{
api::{StatusInfo, StatusType},
Spread, SpreadScalerProperty, TraitProperty,
};
use crate::{
commands::{Command, StartProvider, StopProvider},
events::{
Event, HostHeartbeat, HostStarted, HostStopped, ProviderInfo, ProviderStarted,
ConfigSet, Event, HostHeartbeat, HostStarted, HostStopped, ProviderHealthCheckFailed,
ProviderHealthCheckInfo, ProviderHealthCheckPassed, ProviderInfo, ProviderStarted,
ProviderStopped,
},
scaler::{
@ -22,11 +26,11 @@ use crate::{
},
Scaler,
},
storage::{Host, ReadStore},
storage::{Host, Provider, ProviderStatus, ReadStore},
SCALER_KEY,
};
pub const PROVIDER_SPREAD_SCALER_TYPE: &str = "providerspreadscaler";
use super::SPREAD_SCALER_KIND;
/// Config for a ProviderSpreadConfig
#[derive(Clone)]
@ -67,6 +71,14 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ProviderSpreadScaler<S> {
&self.id
}
fn kind(&self) -> &str {
SPREAD_SCALER_KIND
}
fn name(&self) -> String {
self.config.provider_id.to_string()
}
async fn status(&self) -> StatusInfo {
let _ = self.reconcile().await;
self.status.read().await.to_owned()
@ -107,6 +119,65 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ProviderSpreadScaler<S> {
{
self.reconcile().await
}
// perform status updates for health check events
Event::ProviderHealthCheckFailed(ProviderHealthCheckFailed {
data: ProviderHealthCheckInfo { provider_id, .. },
})
| Event::ProviderHealthCheckPassed(ProviderHealthCheckPassed {
data: ProviderHealthCheckInfo { provider_id, .. },
}) if provider_id == &self.config.provider_id => {
let provider = self
.store
.get::<Provider>(&self.config.lattice_id, &self.config.provider_id)
.await?;
let unhealthy_providers = provider.map_or(0, |p| {
p.hosts
.values()
.filter(|s| *s == &ProviderStatus::Failed)
.count()
});
let status = self.status.read().await.to_owned();
// update health status of scaler
if let Some(status) = match (status, unhealthy_providers > 0) {
// scaler is deployed but contains unhealthy providers
(
StatusInfo {
status_type: StatusType::Deployed,
..
},
true,
) => Some(StatusInfo::failed(&format!(
"Unhealthy provider on {} host(s)",
unhealthy_providers
))),
// scaler can become unhealthy only if it was previously deployed
// once scaler becomes healthy again revert back to deployed state
// this is a workaround to detect unhealthy status until
// StatusType::Unhealthy can be used
(
StatusInfo {
status_type: StatusType::Failed,
message,
},
false,
) if message.starts_with("Unhealthy provider on") => {
Some(StatusInfo::deployed(""))
}
// don't update status if scaler is not deployed
_ => None,
} {
*self.status.write().await = status;
}
// only status needs update no new commands required
Ok(Vec::new())
}
Event::ConfigSet(ConfigSet { config_name })
if self.config.provider_config.contains(config_name) =>
{
self.reconcile().await
}
// No other event impacts the job of this scaler so we can ignore it
_ => Ok(Vec::new()),
}
@ -276,16 +347,21 @@ impl<S: ReadStore + Send + Sync + Clone> Scaler for ProviderSpreadScaler<S> {
trace!(?commands, "Calculated commands for provider scaler");
let status = if spread_status.is_empty() {
StatusInfo::deployed("")
} else {
StatusInfo::failed(
let status = match (spread_status.is_empty(), commands.is_empty()) {
// No failures, no commands, scaler satisfied
(true, true) => StatusInfo::deployed(""),
// No failures, commands generated, scaler is reconciling
(true, false) => {
StatusInfo::reconciling(&format!("Scaling provider on {} host(s)", commands.len()))
}
// Failures occurred, scaler is in a failed state
(false, _) => StatusInfo::failed(
&spread_status
.into_iter()
.map(|s| s.message)
.collect::<Vec<String>>()
.join(" "),
)
),
};
trace!(?status, "Updating scaler status");
*self.status.write().await = status;
@ -319,7 +395,7 @@ impl<S: ReadStore + Send + Sync> ProviderSpreadScaler<S> {
// that make it unique. This is used during upgrades to determine if a
// scaler is the same as a previous one.
let mut id_parts = vec![
PROVIDER_SPREAD_SCALER_TYPE,
SPREAD_SCALER_KIND,
&config.model_name,
component_name,
&config.provider_id,
@ -1193,4 +1269,274 @@ mod test {
Ok(())
}
#[tokio::test]
async fn test_healthy_providers_return_healthy_status() -> Result<()> {
let lattice_id = "test_healthy_providers";
let provider_ref = "fakecloud.azurecr.io/provider:3.2.1".to_string();
let provider_id = "VASDASDIAMAREALPROVIDERPROVIDER";
let host_id_one = "NASDASDIMAREALHOSTONE";
let host_id_two = "NASDASDIMAREALHOSTTWO";
let store = Arc::new(TestStore::default());
store
.store(
lattice_id,
host_id_one.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("cloud".to_string(), "fake".to_string()),
("region".to_string(), "us-noneofyourbusiness-1".to_string()),
]),
providers: HashSet::from_iter([ProviderInfo {
provider_id: provider_id.to_string(),
provider_ref: provider_ref.to_string(),
annotations: BTreeMap::default(),
}]),
uptime_seconds: 123,
version: None,
id: host_id_one.to_string(),
last_seen: Utc::now(),
},
)
.await?;
// Ensure we spread evenly with equal weights, clean division
let multi_spread_even = SpreadScalerProperty {
instances: 2,
spread: vec![Spread {
name: "SimpleOne".to_string(),
requirements: BTreeMap::from_iter([("cloud".to_string(), "fake".to_string())]),
weight: Some(100),
}],
};
let spreadscaler = ProviderSpreadScaler::new(
store.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_string(),
provider_reference: provider_ref.to_string(),
provider_id: provider_id.to_string(),
spread_config: multi_spread_even,
model_name: MODEL_NAME.to_string(),
provider_config: vec!["foobar".to_string()],
},
"fake_component",
);
store
.store(
lattice_id,
host_id_two.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("cloud".to_string(), "fake".to_string()),
("region".to_string(), "us-yourhouse-1".to_string()),
]),
providers: HashSet::from_iter([ProviderInfo {
provider_id: provider_id.to_string(),
provider_ref: provider_ref.to_string(),
annotations: spreadscaler_annotations("SimpleOne", spreadscaler.id()),
}]),
uptime_seconds: 123,
version: None,
id: host_id_two.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
provider_id.to_string(),
Provider {
id: provider_id.to_string(),
name: "provider".to_string(),
issuer: "issuer".to_string(),
reference: provider_ref.to_string(),
hosts: HashMap::from([
(host_id_one.to_string(), ProviderStatus::Failed),
(host_id_two.to_string(), ProviderStatus::Running),
]),
},
)
.await?;
spreadscaler.reconcile().await?;
spreadscaler
.handle_event(&Event::ProviderHealthCheckFailed(
ProviderHealthCheckFailed {
data: ProviderHealthCheckInfo {
provider_id: provider_id.to_string(),
host_id: host_id_one.to_string(),
},
},
))
.await?;
store
.store(
lattice_id,
provider_id.to_string(),
Provider {
id: provider_id.to_string(),
name: "provider".to_string(),
issuer: "issuer".to_string(),
reference: provider_ref.to_string(),
hosts: HashMap::from([
(host_id_one.to_string(), ProviderStatus::Pending),
(host_id_two.to_string(), ProviderStatus::Running),
]),
},
)
.await?;
spreadscaler
.handle_event(&Event::ProviderHealthCheckPassed(
ProviderHealthCheckPassed {
data: ProviderHealthCheckInfo {
provider_id: provider_id.to_string(),
host_id: host_id_two.to_string(),
},
},
))
.await?;
assert_eq!(
spreadscaler.status.read().await.to_owned(),
StatusInfo::deployed("")
);
Ok(())
}
#[tokio::test]
async fn test_unhealthy_providers_return_unhealthy_status() -> Result<()> {
let lattice_id = "test_unhealthy_providers";
let provider_ref = "fakecloud.azurecr.io/provider:3.2.1".to_string();
let provider_id = "VASDASDIAMAREALPROVIDERPROVIDER";
let host_id_one = "NASDASDIMAREALHOSTONE";
let host_id_two = "NASDASDIMAREALHOSTTWO";
let store = Arc::new(TestStore::default());
store
.store(
lattice_id,
host_id_one.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("cloud".to_string(), "fake".to_string()),
("region".to_string(), "us-noneofyourbusiness-1".to_string()),
]),
providers: HashSet::from_iter([ProviderInfo {
provider_id: provider_id.to_string(),
provider_ref: provider_ref.to_string(),
annotations: BTreeMap::default(),
}]),
uptime_seconds: 123,
version: None,
id: host_id_one.to_string(),
last_seen: Utc::now(),
},
)
.await?;
// Ensure we spread evenly with equal weights, clean division
let multi_spread_even = SpreadScalerProperty {
instances: 2,
spread: vec![Spread {
name: "SimpleOne".to_string(),
requirements: BTreeMap::from_iter([("cloud".to_string(), "fake".to_string())]),
weight: Some(100),
}],
};
let spreadscaler = ProviderSpreadScaler::new(
store.clone(),
ProviderSpreadConfig {
lattice_id: lattice_id.to_string(),
provider_reference: provider_ref.to_string(),
provider_id: provider_id.to_string(),
spread_config: multi_spread_even,
model_name: MODEL_NAME.to_string(),
provider_config: vec!["foobar".to_string()],
},
"fake_component",
);
store
.store(
lattice_id,
host_id_two.to_string(),
Host {
components: HashMap::new(),
friendly_name: "hey".to_string(),
labels: HashMap::from_iter([
("cloud".to_string(), "fake".to_string()),
("region".to_string(), "us-yourhouse-1".to_string()),
]),
providers: HashSet::from_iter([ProviderInfo {
provider_id: provider_id.to_string(),
provider_ref: provider_ref.to_string(),
annotations: spreadscaler_annotations("SimpleOne", spreadscaler.id()),
}]),
uptime_seconds: 123,
version: None,
id: host_id_two.to_string(),
last_seen: Utc::now(),
},
)
.await?;
store
.store(
lattice_id,
provider_id.to_string(),
Provider {
id: provider_id.to_string(),
name: "provider".to_string(),
issuer: "issuer".to_string(),
reference: provider_ref.to_string(),
hosts: HashMap::from([
(host_id_one.to_string(), ProviderStatus::Failed),
(host_id_two.to_string(), ProviderStatus::Running),
]),
},
)
.await?;
spreadscaler.reconcile().await?;
spreadscaler
.handle_event(&Event::ProviderHealthCheckFailed(
ProviderHealthCheckFailed {
data: ProviderHealthCheckInfo {
provider_id: provider_id.to_string(),
host_id: host_id_one.to_string(),
},
},
))
.await?;
assert_eq!(
spreadscaler.status.read().await.to_owned(),
StatusInfo::failed("Unhealthy provider on 1 host(s)")
);
Ok(())
}
}

View File

@ -0,0 +1,66 @@
use anyhow::Result;
use async_trait::async_trait;
use wadm_types::{api::StatusInfo, TraitProperty};
use crate::{commands::Command, events::Event, scaler::Scaler};
/// The StatusScaler is a scaler that only reports a predefined status and does not perform any actions.
/// It's primarily used as a placeholder for a scaler that wadm failed to initialize for reasons that
/// couldn't be caught during deployment, and will not be fixed until a new version of the app is deployed.
pub struct StatusScaler {
id: String,
kind: String,
name: String,
status: StatusInfo,
}
#[async_trait]
impl Scaler for StatusScaler {
fn id(&self) -> &str {
&self.id
}
fn kind(&self) -> &str {
&self.kind
}
fn name(&self) -> String {
self.name.to_string()
}
async fn status(&self) -> StatusInfo {
self.status.clone()
}
async fn update_config(&mut self, _config: TraitProperty) -> Result<Vec<Command>> {
Ok(vec![])
}
async fn handle_event(&self, _event: &Event) -> Result<Vec<Command>> {
Ok(Vec::with_capacity(0))
}
async fn reconcile(&self) -> Result<Vec<Command>> {
Ok(Vec::with_capacity(0))
}
async fn cleanup(&self) -> Result<Vec<Command>> {
Ok(Vec::with_capacity(0))
}
}
impl StatusScaler {
pub fn new(
id: impl AsRef<str>,
kind: impl AsRef<str>,
name: impl AsRef<str>,
status: StatusInfo,
) -> Self {
StatusScaler {
id: id.as_ref().to_string(),
kind: kind.as_ref().to_string(),
name: name.as_ref().to_string(),
status,
}
}
}

View File

@ -2,19 +2,20 @@ use std::collections::HashMap;
use anyhow::anyhow;
use async_nats::{jetstream::stream::Stream, Client, Message, Subject};
use base64::{engine::general_purpose::STANDARD as B64decoder, Engine};
use serde_json::json;
use tracing::{debug, error, instrument, log::warn, trace};
use tracing::{debug, error, instrument, trace};
use wadm_types::api::{ModelSummary, StatusInfo, StatusType};
use wadm_types::validation::{is_valid_manifest_name, validate_manifest_version, ValidationOutput};
use wadm_types::{
api::{
DeleteModelRequest, DeleteModelResponse, DeleteResult, DeployModelRequest,
DeployModelResponse, DeployResult, GetModelRequest, GetModelResponse, GetResult,
PutModelResponse, PutResult, Status, StatusInfo, StatusResponse, StatusResult, StatusType,
ListModelsResponse, PutModelResponse, PutResult, Status, StatusResponse, StatusResult,
UndeployModelRequest, VersionInfo, VersionResponse,
},
CapabilityProperties, Manifest, Properties,
};
use wadm_types::{ComponentProperties, LATEST_VERSION};
use crate::{model::StoredManifest, publisher::Publisher};
@ -68,8 +69,7 @@ impl<P: Publisher> Handler<P> {
self.send_error(
msg.reply,
format!(
"Manifest name {} contains invalid characters. Manifest names can only contain alphanumeric characters, dashes, and underscores.",
manifest_name
"Manifest name {manifest_name} contains invalid characters. Manifest names can only contain alphanumeric characters, dashes, and underscores.",
),
)
.await;
@ -88,11 +88,47 @@ impl<P: Publisher> Handler<P> {
}
};
if let Some(error_message) = validate_manifest(manifest.clone()).await.err() {
if let Some(error_message) = validate_manifest(&manifest).await.err() {
self.send_error(msg.reply, error_message.to_string()).await;
return;
}
let all_stored_manifests = self
.store
.list(account_id, lattice_id)
.await
.unwrap_or_default();
let deployed_shared_apps: Vec<&Manifest> = all_stored_manifests
.iter()
// Only keep deployed, shared applications
.filter(|manifest| {
manifest.deployed_version().is_some() && manifest.get_current().shared()
})
.map(|manifest| manifest.get_current())
.collect();
// NOTE(brooksmtownsend): You can put an application with missing shared components, because
// the time where you truly need them is when you deploy the application. This can cause a bit
// of friction when it comes to deploy, but it avoids the frustrating race condition where you
// - Put the application looking for a deployed shared component
// - Undeploy the application with the shared component
// - Deploy the new application looking for the shared component (error)
let missing_shared_components = manifest.missing_shared_components(&deployed_shared_apps);
let message = if missing_shared_components.is_empty() {
format!(
"Successfully put manifest {} {}",
manifest_name,
current_manifests.current_version().to_owned()
)
} else {
format!(
"Successfully put manifest {} {}, but some shared components are not deployed: {:?}",
manifest_name,
current_manifests.current_version().to_owned(),
missing_shared_components
)
};
let incoming_version = manifest.version().to_owned();
if !current_manifests.add_version(manifest) {
self.send_error(
@ -113,11 +149,7 @@ impl<P: Publisher> Handler<P> {
},
name: manifest_name.clone(),
total_versions: current_manifests.count(),
message: format!(
"Successfully put manifest {} {}",
manifest_name,
current_manifests.current_version()
),
message,
};
trace!(total_manifests = %resp.total_versions, "Storing manifests");
@ -234,9 +266,16 @@ impl<P: Publisher> Handler<P> {
.await
}
// NOTE: This is the same as list_models but responds with just the list of models instead of using
// the new wrapper type. This can be removed in 0.15.0 once clients all query the new subject
#[instrument(level = "debug", skip(self, msg))]
pub async fn list_models(&self, msg: Message, account_id: Option<&str>, lattice_id: &str) {
let mut data = match self.store.list(account_id, lattice_id).await {
pub(crate) async fn list_models_deprecated(
&self,
msg: Message,
account_id: Option<&str>,
lattice_id: &str,
) {
let stored_manifests = match self.store.list(account_id, lattice_id).await {
Ok(d) => d,
Err(e) => {
error!(error = %e, "Unable to fetch data");
@ -246,20 +285,61 @@ impl<P: Publisher> Handler<P> {
}
};
for model in &mut data {
if let Some(status) = self.get_manifest_status(lattice_id, &model.name).await {
model.status = status.status_type;
model.status_message = Some(status.message);
} else {
warn!("Could not fetch status for application, assuming undeployed");
model.status = StatusType::Undeployed;
model.status_message = None;
}
}
let application_summaries = stored_manifests.into_iter().map(|manifest| async {
let status = self
.get_manifest_status(lattice_id, manifest.name())
.await
.unwrap_or_else(|| {
Status::new(StatusInfo::waiting(
"Waiting for status: Lattice contains no hosts, deployment not started.",
), vec![])
});
summary_from_manifest_status(manifest, status)
});
let models: Vec<ModelSummary> = futures::future::join_all(application_summaries).await;
// NOTE: We _just_ deserialized this from the store above and then manually constructed it,
// so we should be just fine. Just in case though, we unwrap to default
self.send_reply(msg.reply, serde_json::to_vec(&data).unwrap_or_default())
self.send_reply(msg.reply, serde_json::to_vec(&models).unwrap_or_default())
.await
}
#[instrument(level = "debug", skip(self, msg))]
pub async fn list_models(&self, msg: Message, account_id: Option<&str>, lattice_id: &str) {
let stored_manifests = match self.store.list(account_id, lattice_id).await {
Ok(d) => d,
Err(e) => {
error!(error = %e, "Unable to fetch data");
self.send_error(msg.reply, "Internal storage error".to_string())
.await;
return;
}
};
let application_summaries = stored_manifests.into_iter().map(|manifest| async {
let status = self
.get_manifest_status(lattice_id, manifest.name())
.await
.unwrap_or_else(|| {
Status::new(StatusInfo::waiting(
"Waiting for status: Lattice contains no hosts, deployment not started.",
), vec![])
});
summary_from_manifest_status(manifest, status)
});
let models: Vec<ModelSummary> = futures::future::join_all(application_summaries).await;
let reply = ListModelsResponse {
result: GetResult::Success,
message: "Successfully fetched list of applications".to_string(),
models,
};
// NOTE: We _just_ deserialized this from the store above and then manually constructed it,
// so we should be just fine. Just in case though, we unwrap to default
self.send_reply(msg.reply, serde_json::to_vec(&reply).unwrap_or_default())
.await
}
@ -332,97 +412,110 @@ impl<P: Publisher> Handler<P> {
}
}
};
let reply_data = if let Some(version) = req.version {
// TODO(#451): if shared and deployed, make sure that no other shared apps are using it
let reply_data = {
match self.store.get(account_id, lattice_id, name).await {
Ok(Some((mut current, current_revision))) => {
let deleted = current.delete_version(&version);
if deleted && !current.is_empty() {
// If the version we deleted was the deployed one, undeploy it
let deployed_version = current.deployed_version();
let undeploy = if deployed_version.map(|v| v == version).unwrap_or(false) {
trace!(?deployed_version, deleted_version = %version, "Deployed version matches deleted. Will undeploy");
current.undeploy();
true
if let Some(version) = req.version {
let deleted = current.delete_version(&version);
if deleted && !current.is_empty() {
// If the version we deleted was the deployed one, undeploy it
let deployed_version = current.deployed_version();
let undeploy = if deployed_version
.map(|v| v == version)
.unwrap_or(false)
{
trace!(?deployed_version, deleted_version = %version, "Deployed version matches deleted. Will undeploy");
current.undeploy();
true
} else {
trace!(?deployed_version, deleted_version = %version, "Deployed version does not match deleted version. Will not undeploy");
false
};
self.store
.set(account_id, lattice_id, current, Some(current_revision))
.await
.map(|_| DeleteModelResponse {
result: DeleteResult::Deleted,
message: format!(
"Successfully deleted version {version} of application {name}"
),
undeploy,
})
.unwrap_or_else(|e| {
error!(error = %e, "Unable to delete data");
DeleteModelResponse {
result: DeleteResult::Error,
message: format!(
"Internal storage error when deleting {version} of application {name}"
),
undeploy: false,
}
})
} else if deleted && current.is_empty() {
// If we deleted the last one, delete the model from the store
self.store
.delete(account_id, lattice_id, name)
.await
.map(|_| DeleteModelResponse {
result: DeleteResult::Deleted,
message: format!(
"Successfully deleted last version of application {name}"
),
// By default if it is all gone, we definitely undeployed things
undeploy: true,
})
.unwrap_or_else(|e| {
error!(error = %e, "Unable to delete data");
DeleteModelResponse {
result: DeleteResult::Deleted,
message: format!(
"Internal storage error when deleting {version} of application {name}"
),
undeploy: false,
}
})
} else {
trace!(?deployed_version, deleted_version = %version, "Deployed version does not match deleted version. Will not undeploy");
false
};
self.store
.set(account_id, lattice_id, current, Some(current_revision))
.await
.map(|_| DeleteModelResponse {
result: DeleteResult::Deleted,
message: format!(
"Successfully deleted version {version} of application {name}"
),
undeploy,
})
.unwrap_or_else(|e| {
DeleteModelResponse {
result: DeleteResult::Noop,
message: format!("Application version {version} doesn't exist"),
undeploy: false,
}
}
} else {
match self.store.delete(account_id, lattice_id, name).await {
Ok(_) => {
DeleteModelResponse {
result: DeleteResult::Deleted,
message: format!("Successfully deleted application {name}"),
// By default if it is all gone, we definitely undeployed things
undeploy: true,
}
}
Err(e) => {
error!(error = %e, "Unable to delete data");
DeleteModelResponse {
result: DeleteResult::Error,
message: "Internal storage error".to_string(),
message: format!(
"Internal storage error when deleting application {name}"
),
undeploy: false,
}
})
} else if deleted && current.is_empty() {
// If we deleted the last one, delete the model from the store
self.store
.delete(account_id, lattice_id, name)
.await
.map(|_| DeleteModelResponse {
result: DeleteResult::Deleted,
message: format!(
"Successfully deleted last version of application {name}"
),
// By default if it is all gone, we definitely undeployed things
undeploy: true,
})
.unwrap_or_else(|e| {
error!(error = %e, "Unable to delete data");
DeleteModelResponse {
result: DeleteResult::Deleted,
message: "Internal storage error".to_string(),
undeploy: false,
}
})
} else {
DeleteModelResponse {
result: DeleteResult::Noop,
message: format!("Application version {version} doesn't exist"),
undeploy: false,
}
}
}
}
Ok(None) => DeleteModelResponse {
result: DeleteResult::Noop,
message: format!("Application {name} doesn't exist"),
message: format!("Application {name} doesn't exist or was already deleted"),
undeploy: false,
},
Err(e) => {
error!(error = %e, "Unable to fetch current data data");
error!(error = %e, "Unable to fetch current manifest data for application {name}");
DeleteModelResponse {
result: DeleteResult::Error,
message: "Internal storage error".to_string(),
undeploy: false,
}
}
}
} else {
match self.store.delete(account_id, lattice_id, name).await {
Ok(_) => {
DeleteModelResponse {
result: DeleteResult::Deleted,
message: format!("Successfully deleted application {}", name),
// By default if it is all gone, we definitely undeployed things
undeploy: true,
}
}
Err(e) => {
error!(error = %e, "Unable to delete data");
DeleteModelResponse {
result: DeleteResult::Error,
message: "Internal storage error".to_string(),
message: format!("Internal storage error while fetching manifest data for application {name}"),
undeploy: false,
}
}
@ -499,6 +592,8 @@ impl<P: Publisher> Handler<P> {
serde_json::to_vec(&DeployModelResponse {
result: DeployResult::NotFound,
message: format!("Application with the name {name} not found"),
name: name.to_string(),
version: req.version.clone(),
})
.unwrap_or_default(),
)
@ -524,14 +619,42 @@ impl<P: Publisher> Handler<P> {
}
};
// Retrieve all the existing provider refs in store that are currently deployed
let mut existing_provider_refs: HashMap<String, (String, String)> = HashMap::new();
// Fetch the model that's being staged for deployment for validation
let staged_model = match req.version.clone() {
Some(v) if v == LATEST_VERSION => manifests.get_current(),
Some(v) => {
if let Some(model) = manifests.get_version(&v) {
model
} else {
trace!("Requested version does not exist");
self.send_reply(
msg.reply,
// NOTE: We are constructing all data here, so this shouldn't fail, but just in
// case we unwrap to nothing
serde_json::to_vec(&DeployModelResponse {
result: DeployResult::Error,
message: format!("Application with the name '{name}' does not have a version '{v}' to deploy"),
name: name.to_string(),
version: Some(v.to_string()),
})
.unwrap_or_default(),
)
.await;
return;
}
}
// Get the current version if payload version is None, since deploy() does the same
None => manifests.get_current(),
};
// Retrieve all the existing identifiers of deployed components and providers, and check if the staged model has any duplicates
let mut existing_ids: HashMap<String, String> = HashMap::new();
for model_summary in stored_models.iter() {
// Excluding models that do not have a deployed version at present
if model_summary.deployed_version.is_some() {
if model_summary.deployed_version().is_some() {
let (stored_manifest, _) = match self
.store
.get(account_id, lattice_id, &model_summary.name)
.get(account_id, lattice_id, &model_summary.name())
.await
{
Ok(Some(m)) => m,
@ -545,23 +668,20 @@ impl<P: Publisher> Handler<P> {
};
// Performing checks against all other manifests except previous versions of the current manifest
// Because upgrading versions is a valid case for adding providers of updated versions
// Because upgrading versions is a valid case for carrying over the same identifiers
if stored_manifest.name() != name {
if let Some(deployed_manifest) = stored_manifest.get_deployed() {
for component in deployed_manifest.spec.components.iter() {
if let Properties::Capability {
properties:
CapabilityProperties {
image: image_name, ..
},
} = &component.properties
{
if let Some((ref_link, ref_version)) = parse_image_ref(image_name) {
existing_provider_refs.insert(
ref_link,
(ref_version, stored_manifest.name().to_string()),
);
}
let (Properties::Capability {
properties: CapabilityProperties { id, .. },
}
| Properties::Component {
properties: ComponentProperties { id, .. },
}) = &component.properties;
if let Some(id) = id.as_ref() {
existing_ids
.insert(id.to_string(), stored_manifest.name().to_string());
}
}
};
@ -569,7 +689,54 @@ impl<P: Publisher> Handler<P> {
}
}
if !manifests.deploy(req.version) {
// Compare if any of the identifiers in the staged model are duplicates
for component in staged_model.spec.components.iter() {
let (Properties::Capability {
properties: CapabilityProperties { id, .. },
}
| Properties::Component {
properties: ComponentProperties { id, .. },
}) = &component.properties;
if let Some(id) = id.as_ref() {
if let Some(conflicting_manifest_name) = existing_ids.get(id) {
error!(
id,
conflicting_manifest_name,
"Component identifier is already deployed in a different application.",
);
self.send_error(
msg.reply,
format!(
"Component identifier '{id}' is already deployed in a different application '{conflicting_manifest_name}'."
),
)
.await;
return;
}
}
}
// TODO(#451): If this app is shared, or the previous version was, make sure that shared
// components that have dependent applications are still present
let deployed_apps: Vec<&Manifest> = stored_models
.iter()
.filter(|a| a.deployed_version().is_some() && a.get_current().shared())
.map(|a| a.get_current())
.collect();
let missing_shared_components = staged_model.missing_shared_components(&deployed_apps);
// Ensure all shared components point to a valid component that is deployed in another application
if !missing_shared_components.is_empty() {
self.send_error(
msg.reply,
format!("Application contains shared components that are not deployed in other applications: {:?}", missing_shared_components.iter().map(|c| &c.name).collect::<Vec<_>>())
).await;
return;
}
if !manifests.deploy(req.version.clone()) {
trace!("Requested version does not exist");
self.send_reply(
msg.reply,
@ -580,6 +747,8 @@ impl<P: Publisher> Handler<P> {
message: format!(
"Application with the name {name} does not have the specified version to deploy"
),
name: name.to_string(),
version: req.version,
})
.unwrap_or_default(),
)
@ -592,6 +761,7 @@ impl<P: Publisher> Handler<P> {
.unwrap()
.to_owned();
let manifest_version = manifest.version().to_string();
let reply = self
.store
.set(account_id, lattice_id, manifests, Some(current_revision))
@ -602,12 +772,16 @@ impl<P: Publisher> Handler<P> {
"Successfully deployed application {name} {}",
manifest.version()
),
name: name.to_string(),
version: Some(manifest_version.clone()),
})
.unwrap_or_else(|e| {
error!(error = %e, "Unable to store updated data");
DeployModelResponse {
result: DeployResult::Error,
message: "Internal storage error".to_string(),
name: name.to_string(),
version: Some(manifest_version.clone()),
}
});
trace!("Manifest saved in store, sending notification");
@ -620,6 +794,8 @@ impl<P: Publisher> Handler<P> {
serde_json::to_vec(&DeployModelResponse {
result: DeployResult::Error,
message: "Error notifying processors of newly deployed manifest. This is likely a transient error, so please retry the request".to_string(),
name: name.to_string(),
version: Some(manifest_version),
})
.unwrap_or_default(),
)
@ -673,6 +849,8 @@ impl<P: Publisher> Handler<P> {
serde_json::to_vec(&DeployModelResponse {
result: DeployResult::NotFound,
message: format!("Application with the name {name} not found"),
name: name.to_string(),
version: None,
})
.unwrap_or_default(),
)
@ -686,6 +864,7 @@ impl<P: Publisher> Handler<P> {
return;
}
};
// TODO(#451): if shared, make sure that no other shared apps are using it
let reply = if manifests.undeploy() {
trace!("Manifest undeployed. Storing updated manifest");
@ -696,12 +875,16 @@ impl<P: Publisher> Handler<P> {
.map(|_| DeployModelResponse {
result: DeployResult::Acknowledged,
message: format!("Successfully undeployed application {name}"),
name: name.to_string(),
version: None,
})
.unwrap_or_else(|e| {
error!(error = %e, "Unable to store updated data");
DeployModelResponse {
result: DeployResult::Error,
message: "Internal storage error".to_string(),
name: name.to_string(),
version: None,
}
})
} else {
@ -709,6 +892,8 @@ impl<P: Publisher> Handler<P> {
DeployModelResponse {
result: DeployResult::Acknowledged,
message: format!("Application {name} was already undeployed"),
name: name.to_string(),
version: None,
}
};
// We always want to resend in an undeploy in case things failed last time
@ -723,6 +908,8 @@ impl<P: Publisher> Handler<P> {
serde_json::to_vec(&DeployModelResponse {
result: DeployResult::Error,
message: "Error notifying processors of undeployed manifest. This is likely a transient error, so please retry the request".to_string(),
name: name.to_string(),
version: None,
})
.unwrap_or_default(),
)
@ -748,42 +935,11 @@ impl<P: Publisher> Handler<P> {
lattice_id: &str,
name: &str,
) {
trace!("Fetching current manifest from store");
let manifests: StoredManifest = match self.store.get(account_id, lattice_id, name).await {
Ok(Some((m, _))) => m,
Ok(None) => {
self.send_reply(
msg.reply,
// NOTE: We are constructing all data here, so this shouldn't fail, but just in
// case we unwrap to nothing
serde_json::to_vec(&StatusResponse {
result: StatusResult::NotFound,
message: format!("Application with the name {name} not found"),
status: None,
})
.unwrap_or_default(),
)
.await;
return;
}
Err(e) => {
error!(error = %e, "Unable to fetch data");
self.send_error(msg.reply, "Internal storage error".to_string())
.await;
return;
}
};
let current = manifests.get_current();
let status = Status {
version: current.version().to_owned(),
info: self
.get_manifest_status(lattice_id, name)
.await
.unwrap_or_default(),
components: vec![],
};
trace!("Fetching current manifest status");
let status = self
.get_manifest_status(lattice_id, name)
.await
.unwrap_or_default();
self.send_reply(
msg.reply,
@ -833,28 +989,46 @@ impl<P: Publisher> Handler<P> {
self.send_reply(reply, response).await;
}
async fn get_manifest_status(&self, lattice_id: &str, name: &str) -> Option<StatusInfo> {
async fn get_manifest_status(&self, lattice_id: &str, name: &str) -> Option<Status> {
// NOTE(brooksmtownsend): We're getting the last raw message instead of direct get here
// to ensure we fetch the latest message from the cluster leader.
match self
.status_stream
.get_last_raw_message_by_subject(&format!("wadm.status.{lattice_id}.{name}",))
.await
.map(|raw| {
B64decoder
.decode(raw.payload)
.map(|b| serde_json::from_slice::<StatusInfo>(&b))
}) {
Ok(Ok(Ok(status))) => Some(status),
// Model status doesn't exist or is invalid, assuming undeployed
.map(|status_msg| serde_json::from_slice::<Status>(&status_msg.payload))
{
Ok(Ok(status)) => Some(status),
// Application status doesn't exist or is invalid, assuming undeployed
_ => None,
}
}
}
/// Helper function to create a [`ModelSummary`] from a [`StoredManifest`] and [`Status`]
fn summary_from_manifest_status(manifest: StoredManifest, status: Status) -> ModelSummary {
// TODO: Remove in 0.14.0. This is to ensure that older clients that don't
// understand the `Waiting` status type can still deserialize the ModelSummary
let status_type = if status.info.status_type == StatusType::Waiting {
StatusType::Undeployed
} else {
status.info.status_type
};
#[allow(deprecated)]
ModelSummary {
name: manifest.name().to_owned(),
version: manifest.current_version().to_owned(),
description: manifest.get_current().description().map(|s| s.to_owned()),
deployed_version: manifest.get_deployed().map(|m| m.version().to_owned()),
status: status_type,
status_message: Some(status.info.message.to_owned()),
detailed_status: status,
}
}
// Manifest validation
pub(crate) async fn validate_manifest(manifest: Manifest) -> anyhow::Result<()> {
let failures = wadm_types::validation::validate_manifest(&manifest).await?;
pub(crate) async fn validate_manifest(manifest: &Manifest) -> anyhow::Result<()> {
let failures = wadm_types::validation::validate_manifest(manifest).await?;
for failure in failures {
if matches!(
failure.level,
@ -867,15 +1041,6 @@ pub(crate) async fn validate_manifest(manifest: Manifest) -> anyhow::Result<()>
Ok(())
}
fn parse_image_ref(image_name: &str) -> Option<(String, String)> {
if let Some((repository_reference, ref_version)) = image_name.split_once(':') {
Some((repository_reference.to_owned(), ref_version.to_owned()))
} else {
// ignore if image ref is not in the format some_url:some_version
None
}
}
#[cfg(test)]
mod test {
use std::io::BufReader;
@ -895,15 +1060,15 @@ mod test {
#[tokio::test]
async fn test_manifest_validation() {
let correct_manifest =
deserialize_yaml("./oam/simple1.yaml").expect("Should be able to parse");
assert!(validate_manifest(correct_manifest).await.is_ok());
let manifest = deserialize_yaml("./test/data/incorrect_component.yaml")
let correct_manifest = deserialize_yaml("../../tests/fixtures/manifests/simple.yaml")
.expect("Should be able to parse");
match validate_manifest(manifest).await {
assert!(validate_manifest(&correct_manifest).await.is_ok());
let manifest = deserialize_yaml("../../tests/fixtures/manifests/incorrect_component.yaml")
.expect("Should be able to parse");
match validate_manifest(&manifest).await {
Ok(()) => panic!("Should have detected incorrect component"),
Err(e) => {
assert!(e
@ -912,20 +1077,20 @@ mod test {
}
}
let manifest = deserialize_yaml("./test/data/duplicate_component.yaml")
let manifest = deserialize_yaml("../../tests/fixtures/manifests/duplicate_component.yaml")
.expect("Should be able to parse");
match validate_manifest(manifest).await {
match validate_manifest(&manifest).await {
Ok(()) => panic!("Should have detected duplicate component"),
Err(e) => assert!(e
.to_string()
.contains("Duplicate component name in manifest")),
}
let manifest =
deserialize_yaml("./test/data/duplicate_id1.yaml").expect("Should be able to parse");
let manifest = deserialize_yaml("../../tests/fixtures/manifests/duplicate_id1.yaml")
.expect("Should be able to parse");
match validate_manifest(manifest).await {
match validate_manifest(&manifest).await {
Ok(()) => {
panic!("Should have detected duplicate component ID in provider properties")
}
@ -934,25 +1099,55 @@ mod test {
.contains("Duplicate component identifier in manifest")),
}
let manifest =
deserialize_yaml("./test/data/duplicate_id2.yaml").expect("Should be able to parse");
let manifest = deserialize_yaml("../../tests/fixtures/manifests/duplicate_id2.yaml")
.expect("Should be able to parse");
match validate_manifest(manifest).await {
match validate_manifest(&manifest).await {
Ok(()) => panic!("Should have detected duplicate component ID in component properties"),
Err(e) => assert!(e
.to_string()
.contains("Duplicate component identifier in manifest")),
}
let manifest = deserialize_yaml("./test/data/missing_capability_component.yaml")
.expect("Should be able to parse");
let manifest =
deserialize_yaml("../../tests/fixtures/manifests/missing_capability_component.yaml")
.expect("Should be able to parse");
match validate_manifest(manifest).await {
match validate_manifest(&manifest).await {
Ok(()) => panic!("Should have detected missing capability component"),
Err(e) => assert!(e
.to_string()
.contains("The following capability component(s) are missing from the manifest: ")),
}
let manifest = deserialize_yaml("../../tests/fixtures/manifests/duplicate_links.yaml")
.expect("Should be able to parse");
match validate_manifest(&manifest).await {
Ok(()) => panic!("Should have detected duplicate links"),
Err(e) => assert!(e
.to_string()
.contains("Duplicate link found inside component")),
}
let manifest =
deserialize_yaml("../../tests/fixtures/manifests/correct_unique_interface_links.yaml")
.expect("Should be able to parse");
assert!(validate_manifest(&manifest).await.is_ok());
let manifest = deserialize_yaml(
"../../tests/fixtures/manifests/incorrect_unique_interface_links.yaml",
)
.expect("Should be able to parse");
match validate_manifest(&manifest).await {
Ok(()) => panic!("Should have detected duplicate interface links"),
Err(e) => assert!(
e.to_string()
.contains("Duplicate link found inside component")
&& e.to_string().contains("atomics"),
"Error should mention duplicate interface"
),
}
}
/// Ensure that a long image ref in a manifest works,
@ -960,7 +1155,7 @@ mod test {
#[tokio::test]
async fn manifest_name_long_image_ref() -> Result<()> {
validate_manifest(
deserialize_yaml("./test/data/long_image_refs.yaml")
&deserialize_yaml("../../tests/fixtures/manifests/long_image_refs.yaml")
.context("failed to deserialize YAML")?,
)
.await

View File

@ -118,7 +118,12 @@ impl<P: Publisher> Server<P> {
category: "model",
operation: "list",
object_name: None,
} => self.handler.list_models(msg, account_id, lattice_id).await,
} => {
warn!("Received deprecated subject: model.list. Please use model.get instead");
self.handler
.list_models_deprecated(msg, account_id, lattice_id)
.await
}
ParsedSubject {
account_id,
lattice_id,
@ -130,6 +135,13 @@ impl<P: Publisher> Server<P> {
.get_model(msg, account_id, lattice_id, name)
.await
}
ParsedSubject {
account_id,
lattice_id,
category: "model",
operation: "get",
object_name: None,
} => self.handler.list_models(msg, account_id, lattice_id).await,
ParsedSubject {
account_id,
lattice_id,

View File

@ -3,7 +3,6 @@ use std::collections::BTreeSet;
use anyhow::Result;
use async_nats::jetstream::kv::{Operation, Store};
use tracing::{debug, instrument, trace};
use wadm_types::api::{ModelSummary, StatusType};
use crate::model::StoredManifest;
@ -94,13 +93,13 @@ impl ModelStorage {
.await
}
/// Fetches a summary of all models in the given lattice.
/// Fetches a summary of all manifests for the given lattice.
#[instrument(level = "debug", skip(self))]
pub async fn list(
&self,
account_id: Option<&str>,
lattice_id: &str,
) -> Result<Vec<ModelSummary>> {
) -> Result<Vec<StoredManifest>> {
debug!("Fetching list of models from storage");
let futs = self
.get_model_set(account_id, lattice_id)
@ -109,23 +108,11 @@ impl ModelStorage {
.0
.into_iter()
// We can't use filter map with futures, but we can use map and then flatten it below
.map(|model_name| {
async {
let manifest = match self.get(account_id, lattice_id, &model_name).await {
Ok(Some((manifest, _))) => manifest,
Ok(None) => return None,
Err(e) => return Some(Err(e)),
};
Some(Ok(ModelSummary {
name: model_name,
version: manifest.current_version().to_owned(),
description: manifest.get_current().description().map(|s| s.to_owned()),
deployed_version: manifest.get_deployed().map(|m| m.version().to_owned()),
// TODO(thomastaylor312): Actually fetch the status info from the stored
// manifest once we figure it out
status: StatusType::default(),
status_message: None,
}))
.map(|model_name| async move {
match self.get(account_id, lattice_id, &model_name).await {
Ok(Some((manifest, _))) => Some(Ok(manifest)),
Ok(None) => None,
Err(e) => Some(Err(e)),
}
});
@ -134,7 +121,7 @@ impl ModelStorage {
.await
.into_iter()
.flatten()
.collect::<Result<Vec<ModelSummary>>>()
.collect::<Result<Vec<StoredManifest>>>()
}
/// Deletes the given model from storage. This also removes the model from the list of all

View File

@ -2,8 +2,9 @@ use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use wadm_types::SecretSourceProperty;
use wasmcloud_control_interface::InterfaceLinkDefinition;
use tracing::debug;
use wasmcloud_control_interface::Link;
use wasmcloud_secrets_types::SecretConfig;
use crate::storage::{Component, Host, Provider, ReadStore, StateKind};
use crate::workers::{ConfigSource, LinkSource, SecretSource};
@ -27,7 +28,7 @@ pub struct SnapshotStore<S, L> {
lattice_source: L,
lattice_id: String,
stored_state: Arc<RwLock<InMemoryData>>,
links: Arc<RwLock<Vec<InterfaceLinkDefinition>>>,
links: Arc<RwLock<Vec<Link>>>,
}
impl<S, L> Clone for SnapshotStore<S, L>
@ -86,7 +87,14 @@ where
.into_iter()
.map(|(key, val)| (key, serde_json::to_value(val).unwrap()))
.collect::<HashMap<_, _>>();
let links = self.lattice_source.get_links().await?;
// If we fail to get the links, that likely just means the lattice source is down, so we
// just fall back on what we have cached
if let Ok(links) = self.lattice_source.get_links().await {
*self.links.write().await = links;
} else {
debug!("Failed to get links from lattice source, using cached links");
};
{
let mut stored_state = self.stored_state.write().await;
@ -95,8 +103,6 @@ where
stored_state.insert(Host::KIND.to_owned(), hosts);
}
*self.links.write().await = links;
Ok(())
}
}
@ -159,7 +165,7 @@ where
S: Send + Sync,
L: Send + Sync,
{
async fn get_links(&self) -> anyhow::Result<Vec<InterfaceLinkDefinition>> {
async fn get_links(&self) -> anyhow::Result<Vec<Link>> {
Ok(self.links.read().await.clone())
}
}
@ -181,7 +187,7 @@ where
S: Send + Sync,
L: SecretSource + Send + Sync,
{
async fn get_secret(&self, name: &str) -> anyhow::Result<Option<SecretSourceProperty>> {
async fn get_secret(&self, name: &str) -> anyhow::Result<Option<SecretConfig>> {
self.lattice_source.get_secret(name).await
}
}

View File

@ -1,8 +1,6 @@
use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap, HashSet},
hash::{Hash, Hasher},
};
use std::borrow::{Borrow, ToOwned};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::hash::{Hash, Hasher};
use chrono::{DateTime, Utc};
use semver::Version;
@ -33,7 +31,7 @@ pub struct Provider {
pub hosts: HashMap<String, ProviderStatus>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum ProviderStatus {
/// The provider is starting and hasn't returned a heartbeat yet
Pending,
@ -42,6 +40,7 @@ pub enum ProviderStatus {
/// The provider failed to start
// TODO(thomastaylor312): In the future, we'll probably want to decay out a provider from state
// if it hasn't had a heartbeat
// if it fails a recent health check
Failed,
}
@ -286,8 +285,8 @@ impl From<HostHeartbeat> for Host {
.into_iter()
.map(|component| {
(
component.id, // SAFETY: Unlikely to not fit into a usize, but fallback just in case
component.max_instances.try_into().unwrap_or(usize::MAX),
component.id().into(), // SAFETY: Unlikely to not fit into a usize, but fallback just in case
component.max_instances().try_into().unwrap_or(usize::MAX),
)
})
.collect();
@ -296,12 +295,13 @@ impl From<HostHeartbeat> for Host {
.providers
.into_iter()
.map(|provider| ProviderInfo {
provider_id: provider.id,
provider_id: provider.id().to_string(),
// NOTE: Provider should _always_ have an image ref. The control interface type should be updated.
provider_ref: provider.image_ref.unwrap_or_default(),
provider_ref: provider.image_ref().map(String::from).unwrap_or_default(),
annotations: provider
.annotations
.map(|a| a.into_iter().collect())
.annotations()
.map(ToOwned::to_owned)
.map(BTreeMap::from_iter)
.unwrap_or_default(),
})
.collect();
@ -326,9 +326,9 @@ impl From<&HostHeartbeat> for Host {
.iter()
.map(|component| {
(
component.id.to_owned(),
component.id().to_owned(),
// SAFETY: Unlikely to not fit into a usize, but fallback just in case
component.max_instances.try_into().unwrap_or(usize::MAX),
component.max_instances().try_into().unwrap_or(usize::MAX),
)
})
.collect();
@ -337,12 +337,12 @@ impl From<&HostHeartbeat> for Host {
.providers
.iter()
.map(|provider| ProviderInfo {
provider_id: provider.id.to_owned(),
provider_ref: provider.image_ref.to_owned().unwrap_or_default(),
provider_id: provider.id().to_owned(),
provider_ref: provider.image_ref().map(String::from).unwrap_or_default(),
annotations: provider
.annotations
.clone()
.map(|a| a.into_iter().collect())
.annotations()
.map(ToOwned::to_owned)
.map(BTreeMap::from_iter)
.unwrap_or_default(),
})
.collect();

View File

@ -3,13 +3,14 @@ use std::{collections::HashMap, sync::Arc};
use serde::{de::DeserializeOwned, Serialize};
use tokio::sync::RwLock;
use wadm_types::SecretSourceProperty;
use wasmcloud_control_interface::{HostInventory, InterfaceLinkDefinition};
use wasmcloud_control_interface::{HostInventory, Link};
use wasmcloud_secrets_types::SecretConfig;
use crate::publisher::Publisher;
use crate::storage::StateKind;
use crate::workers::{
Claims, ClaimsSource, ConfigSource, InventorySource, LinkSource, SecretSource,
secret_config_from_map, Claims, ClaimsSource, ConfigSource, InventorySource, LinkSource,
SecretSource,
};
fn generate_key<T: StateKind>(lattice_id: &str) -> String {
@ -110,7 +111,7 @@ impl crate::storage::Store for TestStore {
pub struct TestLatticeSource {
pub claims: HashMap<String, Claims>,
pub inventory: Arc<RwLock<HashMap<String, HostInventory>>>,
pub links: Vec<InterfaceLinkDefinition>,
pub links: Vec<Link>,
pub config: HashMap<String, HashMap<String, String>>,
}
@ -130,7 +131,7 @@ impl InventorySource for TestLatticeSource {
#[async_trait::async_trait]
impl LinkSource for TestLatticeSource {
async fn get_links(&self) -> anyhow::Result<Vec<InterfaceLinkDefinition>> {
async fn get_links(&self) -> anyhow::Result<Vec<Link>> {
Ok(self.links.clone())
}
}
@ -144,19 +145,13 @@ impl ConfigSource for TestLatticeSource {
#[async_trait::async_trait]
impl SecretSource for TestLatticeSource {
async fn get_secret(&self, name: &str) -> anyhow::Result<Option<SecretSourceProperty>> {
let cfg = self
async fn get_secret(&self, name: &str) -> anyhow::Result<Option<SecretConfig>> {
let secret_config = self
.get_config(format!("secret_{name}").as_str())
.await
.map_err(|e| anyhow::anyhow!("{e:?}"))?;
match cfg {
Some(cfg) => {
let cfg: SecretSourceProperty = cfg.try_into()?;
Ok(Some(cfg))
}
None => Ok(None),
}
secret_config.map(secret_config_from_map).transpose()
}
}

View File

@ -74,7 +74,7 @@ impl Worker for CommandWorker {
trace!(command = ?ld, "Handling put linkdef command");
// TODO(thomastaylor312): We should probably change ScopedMessage to allow us `pub`
// access to the inner type so we don't have to clone, but no need to worry for now
self.client.put_link(ld.clone().into()).await
self.client.put_link(ld.clone().try_into()?).await
}
Command::DeleteLink(ld) => {
trace!(command = ?ld, "Handling delete linkdef command");
@ -101,9 +101,11 @@ impl Worker for CommandWorker {
.map_err(|e| anyhow::anyhow!("{e:?}"));
match res {
Ok(ack) if !ack.success => {
Ok(ack) if !ack.succeeded() => {
message.nack().await;
Err(WorkError::Other(anyhow::anyhow!("{}", ack.message).into()))
Err(WorkError::Other(
anyhow::anyhow!("{}", ack.message()).into(),
))
}
Ok(_) => message.ack().await.map_err(WorkError::from),
Err(e) => {

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,14 @@
use anyhow::{bail, Context};
use async_nats::jetstream::stream::Stream;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Debug;
use wasmcloud_secrets_types::SecretConfig;
use tracing::{debug, instrument, trace, warn};
use wadm_types::{api::StatusInfo, SecretSourceProperty};
use wasmcloud_control_interface::{CtlResponse, HostInventory, InterfaceLinkDefinition};
use wadm_types::api::Status;
use wasmcloud_control_interface::{HostInventory, Link};
use crate::{
commands::Command, publisher::Publisher, scaler::secretscaler::SECRET_CONFIG_PREFIX,
APP_SPEC_ANNOTATION,
};
use crate::{commands::Command, publisher::Publisher, APP_SPEC_ANNOTATION};
/// A subset of needed claims to help populate state
#[derive(Debug, Clone)]
@ -44,7 +43,7 @@ pub trait InventorySource {
/// due to testing, but it does allow us to abstract away the concrete type of the client
#[async_trait::async_trait]
pub trait LinkSource {
async fn get_links(&self) -> anyhow::Result<Vec<InterfaceLinkDefinition>>;
async fn get_links(&self) -> anyhow::Result<Vec<Link>>;
}
/// A trait for anything that can fetch a piece of named configuration
@ -60,18 +59,44 @@ pub trait ConfigSource {
/// A trait for anything that can fetch a secret.
#[async_trait::async_trait]
pub trait SecretSource {
async fn get_secret(&self, name: &str) -> anyhow::Result<Option<SecretSourceProperty>>;
async fn get_secret(&self, name: &str) -> anyhow::Result<Option<SecretConfig>>;
}
/// Converts the configuration map of strings to a secret config
pub fn secret_config_from_map(map: HashMap<String, String>) -> anyhow::Result<SecretConfig> {
match (
map.get("name"),
map.get("backend"),
map.get("key"),
map.get("policy"),
map.get("type"),
) {
(None, _, _, _, _) => bail!("missing name field in secret config"),
(_, None, _, _, _) => bail!("missing backend field in secret config"),
(_, _, None, _, _) => bail!("missing key field in secret config"),
(_, _, _, None, _) => bail!("missing policy field in secret config"),
(_, _, _, _, None) => bail!("missing type field in secret config"),
(Some(name), Some(backend), Some(key), Some(policy), Some(secret_type)) => {
Ok(SecretConfig {
name: name.to_string(),
backend: backend.to_string(),
key: key.to_string(),
field: map.get("field").map(|f| f.to_string()),
version: map.get("version").map(|v| v.to_string()),
policy: serde_json::from_str(policy)
.context("failed to deserialize policy from string")?,
secret_type: secret_type.to_string(),
})
}
}
}
#[async_trait::async_trait]
impl ClaimsSource for wasmcloud_control_interface::Client {
async fn get_claims(&self) -> anyhow::Result<HashMap<String, Claims>> {
match self.get_claims().await.map_err(|e| anyhow::anyhow!("{e}")) {
Ok(CtlResponse {
success: true,
response: Some(claims),
..
}) => {
Ok(ctl_resp) if ctl_resp.succeeded() => {
let claims = ctl_resp.data().context("missing claims data")?.to_owned();
Ok(claims
.into_iter()
.filter_map(|mut claim| {
@ -106,13 +131,12 @@ impl InventorySource for wasmcloud_control_interface::Client {
.await
.map_err(|e| anyhow::anyhow!("{e:?}"))?
{
CtlResponse {
success: true,
response: Some(host_inventory),
..
} => Ok(host_inventory),
CtlResponse { message, .. } => Err(anyhow::anyhow!(
"Failed to get inventory for host {host_id}, {message}"
ctl_resp if ctl_resp.succeeded() && ctl_resp.data().is_some() => Ok(ctl_resp
.into_data()
.context("missing host inventory data")?),
ctl_resp => Err(anyhow::anyhow!(
"Failed to get inventory for host {host_id}, {}",
ctl_resp.message()
)),
}
}
@ -124,18 +148,19 @@ impl InventorySource for wasmcloud_control_interface::Client {
// links
#[async_trait::async_trait]
impl LinkSource for wasmcloud_control_interface::Client {
async fn get_links(&self) -> anyhow::Result<Vec<InterfaceLinkDefinition>> {
async fn get_links(&self) -> anyhow::Result<Vec<Link>> {
match self
.get_links()
.await
.map_err(|e| anyhow::anyhow!("{e:?}"))?
{
CtlResponse {
success: true,
response: Some(links),
..
} => Ok(links),
CtlResponse { message, .. } => Err(anyhow::anyhow!("Failed to get links, {message}")),
ctl_resp if ctl_resp.succeeded() && ctl_resp.data().is_some() => {
Ok(ctl_resp.into_data().context("missing link data")?)
}
ctl_resp => Err(anyhow::anyhow!(
"Failed to get links, {}",
ctl_resp.message()
)),
}
}
}
@ -148,15 +173,13 @@ impl ConfigSource for wasmcloud_control_interface::Client {
.await
.map_err(|e| anyhow::anyhow!("{e:?}"))?
{
CtlResponse {
success: true,
response: Some(config),
..
} => Ok(Some(config)),
ctl_resp if ctl_resp.succeeded() && ctl_resp.data().is_some() => {
Ok(ctl_resp.into_data())
}
// TODO(https://github.com/wasmCloud/wasmCloud/issues/1906): The control interface should return a None when config isn't found
// instead of returning an error.
CtlResponse { message, .. } => {
debug!("Failed to get config for {name}, {message}");
ctl_resp => {
debug!("Failed to get config for {name}, {}", ctl_resp.message());
Ok(None)
}
}
@ -165,27 +188,22 @@ impl ConfigSource for wasmcloud_control_interface::Client {
#[async_trait::async_trait]
impl SecretSource for wasmcloud_control_interface::Client {
async fn get_secret(&self, name: &str) -> anyhow::Result<Option<SecretSourceProperty>> {
async fn get_secret(&self, name: &str) -> anyhow::Result<Option<SecretConfig>> {
match self
.get_config(format!("{SECRET_CONFIG_PREFIX}_{name}").as_str())
.get_config(name)
.await
.map_err(|e| anyhow::anyhow!("{e:?}"))?
{
CtlResponse {
success: true,
response: Some(secret),
..
} => Ok(Some(secret.try_into()?)),
CtlResponse {
message,
response: None,
..
} => {
debug!("Failed to get secret for {name}, {message}");
ctl_resp if ctl_resp.succeeded() && ctl_resp.data().is_some() => {
secret_config_from_map(ctl_resp.into_data().context("missing secret data")?)
.map(Some)
}
ctl_resp if ctl_resp.data().is_none() => {
debug!("Failed to get secret for {name}, {}", ctl_resp.message());
Ok(None)
}
CtlResponse { message, .. } => {
debug!("Failed to get secret for {name}, {message}");
ctl_resp => {
debug!("Failed to get secret for {name}, {}", ctl_resp.message());
Ok(None)
}
}
@ -220,7 +238,7 @@ impl<Pub> StatusPublisher<Pub> {
impl<Pub: Publisher> StatusPublisher<Pub> {
#[instrument(level = "trace", skip(self))]
pub async fn publish_status(&self, name: &str, status: StatusInfo) -> anyhow::Result<()> {
pub async fn publish_status(&self, name: &str, status: Status) -> anyhow::Result<()> {
let topic = format!("{}.{name}", self.topic_prefix);
// NOTE(brooksmtownsend): This direct get may not always query the jetstream leader. In the
@ -230,7 +248,7 @@ impl<Pub: Publisher> StatusPublisher<Pub> {
status_stream
.direct_get_last_for_subject(&topic)
.await
.map(|m| serde_json::from_slice::<StatusInfo>(&m.payload).ok())
.map(|m| serde_json::from_slice::<Status>(&m.payload).ok())
.ok()
.flatten()
} else {

View File

@ -1,98 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: complex
annotations:
description: 'This is my CRUDdy complex blobby app with all configuration possibilities'
spec:
policies:
- name: whatever
type: a-sample-policy
properties:
some: value
another: kind
components:
- name: blobby
type: component
properties:
image: ghcr.io/wasmcloud/components/blobby-rust:0.4.0
id: littleblobbytables
config:
- name: defaultcode
properties:
http: '404'
- name: blobby-default-configuration-values
traits:
- type: spreadscaler
properties:
instances: 5
spread:
- name: eastcoast
requirements:
region: us-brooks-east
weight: 40
- name: westcoast
requirements:
region: us-taylor-west
weight: 40
- type: link
properties:
namespace: wasi
package: blobstore
interfaces: [blobstore]
target:
name: fileserver
config:
- name: rootfs
properties:
root: /tmp
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
id: http_server
traits:
- type: spreadscaler
properties:
instances: 3
spread:
- name: westcoast
requirements:
region: us-taylor-west
weight: 40
- name: the-moon
requirements:
region: moon
weight: 20
- type: link
properties:
namespace: wasi
package: http
interfaces: [incoming-handler]
target:
name: blobby
source:
config:
- name: httpaddr
properties:
address: 0.0.0.0:8081
- name: fileserver
type: capability
properties:
image: ghcr.io/wasmcloud/blobstore-fs:0.6.0
id: fileserver
config:
- name: defaultfs
properties:
root: /tmp/blobby
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: the-moon
requirements:
region: moon
weight: 100

View File

@ -1,156 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: petclinic
annotations:
description: "wasmCloud Pet Clinic Sample"
spec:
components:
- name: ui
type: component
properties:
image: wasmcloud.azurecr.io/ui:0.3.2
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: uiclinicapp
requirements:
app: petclinic
- name: customers
type: component
properties:
image: wasmcloud.azurecr.io/customers:0.3.1
traits:
- type: linkdef
properties:
namespace: wasmcloud
package: sql
interfaces: ["query"]
target:
name: postgres
config:
- name: petclinic-connect
properties:
uri: postgres://user:pass@your.db.host.com/petclinic
- type: spreadscaler
properties:
instances: 1
spread:
- name: customersclinicapp
requirements:
app: petclinic
- name: vets
type: component
properties:
image: wasmcloud.azurecr.io/vets:0.3.1
traits:
- type: linkdef
properties:
target:
name: postgres
config:
- name: petclinic-connect
properties:
uri: postgres://user:pass@your.db.host.com/petclinic
- name: foo
namespace: wasmcloud
package: sql
interfaces: ["query"]
- type: linkdef
properties:
target:
name: postgres
config:
- name: petclinic-connect
properties:
uri: postgres://user:pass@your.db.host.com/petclinic
namespace: wasmcloud
package: sql
interfaces: ["query"]
- type: spreadscaler
properties:
instances: 1
spread:
- name: vetsclinicapp
requirements:
app: petclinic
- name: visits
type: component
properties:
image: wasmcloud.azurecr.io/visits:0.3.1
traits:
- type: linkdef
properties:
namespace: wasmcloud
package: sql
interfaces: ["query"]
target:
name: postgres
config:
- name: petclinic-connect
properties:
uri: postgres://user:pass@your.db.host.com/petclinic
- type: spreadscaler
properties:
instances: 1
spread:
- name: visitsclinicapp
requirements:
app: petclinic
- name: clinicapi
type: component
properties:
image: wasmcloud.azurecr.io/clinicapi:0.3.1
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: clinicapp
requirements:
app: petclinic
- name: httpserver
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.16.2
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: httpserverspread
requirements:
app: petclinic
- type: linkdef
properties:
target:
name: clinicapi
namespace: wasi
package: http
interfaces: ["incoming-handler"]
source:
config:
- name: default-port
properties:
address: "0.0.0.0:8080"
- name: postgres
type: capability
properties:
image: wasmcloud.azurecr.io/sqldb-postgres:0.3.1
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: postgresspread
requirements:
app: petclinic

View File

@ -1,35 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: messaging-simple
annotations:
description: "wasmCloud Message Pub Example"
spec:
components:
- name: messagepub
type: component
properties:
image: wasmcloud.azurecr.io/message-pub:0.1.3
traits:
- type: spreadscaler
properties:
instances: 1
- type: linkdef
properties:
target:
name: messaging
- type: linkdef
properties:
target:
name: httpserver
values:
ADDRESS: 0.0.0.0:8080
- name: httpserver
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.18.2
- name: messaging
type: capability
properties:
image: wasmcloud.azurecr.io/nats_messaging:0.17.2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 B

704
flake.lock Normal file
View File

@ -0,0 +1,704 @@
{
"nodes": {
"advisory-db": {
"flake": false,
"locked": {
"lastModified": 1737565911,
"narHash": "sha256-WxIWw1mSPJVU1JfIcTdIubU5UoIwwR8h7UcXop/6htg=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "ffa26704690a3dc403edcd94baef103ee48f66eb",
"type": "github"
},
"original": {
"owner": "rustsec",
"repo": "advisory-db",
"type": "github"
}
},
"advisory-db_2": {
"flake": false,
"locked": {
"lastModified": 1730464311,
"narHash": "sha256-9xJoP1766XJSO1Qr0Lxg2P6dwPncTr3BJYlFMSXBd/E=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "f3460e5ed91658ab94fa41908cfa44991f9f4f02",
"type": "github"
},
"original": {
"owner": "rustsec",
"repo": "advisory-db",
"type": "github"
}
},
"crane": {
"locked": {
"lastModified": 1737689766,
"narHash": "sha256-ivVXYaYlShxYoKfSo5+y5930qMKKJ8CLcAoIBPQfJ6s=",
"owner": "ipetkov",
"repo": "crane",
"rev": "6fe74265bbb6d016d663b1091f015e2976c4a527",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"crane_2": {
"locked": {
"lastModified": 1730652660,
"narHash": "sha256-+XVYfmVXAiYA0FZT7ijHf555dxCe+AoAT5A6RU+6vSo=",
"owner": "ipetkov",
"repo": "crane",
"rev": "a4ca93905455c07cb7e3aca95d4faf7601cba458",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"crane_3": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": [
"wasmcloud",
"nixify",
"nix-log",
"nixify",
"flake-utils"
],
"nixpkgs": [
"wasmcloud",
"nixify",
"nix-log",
"nixify",
"nixpkgs"
],
"rust-overlay": [
"wasmcloud",
"nixify",
"nix-log",
"nixify",
"rust-overlay"
]
},
"locked": {
"lastModified": 1679255352,
"narHash": "sha256-nkGwGuNkhNrnN33S4HIDV5NzkzMLU5mNStRn9sZwq8c=",
"owner": "rvolosatovs",
"repo": "crane",
"rev": "cec65880599a4ec6426186e24342e663464f5933",
"type": "github"
},
"original": {
"owner": "rvolosatovs",
"ref": "feat/wit",
"repo": "crane",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": []
},
"locked": {
"lastModified": 1738132439,
"narHash": "sha256-7q5vsyPQf6/aQEKAOgZ4ggv++Z2ppPSuPCGKlbPcM88=",
"owner": "nix-community",
"repo": "fenix",
"rev": "f94e521c1922784c377a2cace90aa89a6b8a1011",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"fenix_2": {
"inputs": {
"nixpkgs": [
"wasmcloud",
"nixify",
"nixpkgs-nixos"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1731047492,
"narHash": "sha256-F4h8YtTzPWv0/1Z6fc8fMSqKpn7YhOjlgp66cr15tEo=",
"owner": "nix-community",
"repo": "fenix",
"rev": "da6332e801fbb0418f80f20cefa947c5fe5c18c9",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"fenix_3": {
"inputs": {
"nixpkgs": [
"wasmcloud",
"nixify",
"nix-log",
"nixify",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src_2"
},
"locked": {
"lastModified": 1679552560,
"narHash": "sha256-L9Se/F1iLQBZFGrnQJO8c9wE5z0Mf8OiycPGP9Y96hA=",
"owner": "nix-community",
"repo": "fenix",
"rev": "fb49a9f5605ec512da947a21cc7e4551a3950397",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"locked": {
"lastModified": 1678901627,
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"macos-sdk": {
"flake": false,
"locked": {
"lastModified": 1694769349,
"narHash": "sha256-TEvVJy+NMPyzgWSk/6S29ZMQR+ICFxSdS3tw247uhFc=",
"type": "tarball",
"url": "https://github.com/roblabla/MacOSX-SDKs/releases/download/macosx14.0/MacOSX14.0.sdk.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://github.com/roblabla/MacOSX-SDKs/releases/download/macosx14.0/MacOSX14.0.sdk.tar.xz"
}
},
"nix-filter": {
"locked": {
"lastModified": 1730207686,
"narHash": "sha256-SCHiL+1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "776e68c1d014c3adde193a18db9d738458cd2ba4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nix-filter_2": {
"locked": {
"lastModified": 1678109515,
"narHash": "sha256-C2X+qC80K2C1TOYZT8nabgo05Dw2HST/pSn6s+n6BO8=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "aa9ff6ce4a7f19af6415fb3721eaa513ea6c763c",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nix-flake-tests": {
"locked": {
"lastModified": 1677844186,
"narHash": "sha256-ErJZ/Gs1rxh561CJeWP5bohA2IcTq1rDneu1WT6CVII=",
"owner": "antifuchs",
"repo": "nix-flake-tests",
"rev": "bbd9216bd0f6495bb961a8eb8392b7ef55c67afb",
"type": "github"
},
"original": {
"owner": "antifuchs",
"repo": "nix-flake-tests",
"type": "github"
}
},
"nix-flake-tests_2": {
"locked": {
"lastModified": 1677844186,
"narHash": "sha256-ErJZ/Gs1rxh561CJeWP5bohA2IcTq1rDneu1WT6CVII=",
"owner": "antifuchs",
"repo": "nix-flake-tests",
"rev": "bbd9216bd0f6495bb961a8eb8392b7ef55c67afb",
"type": "github"
},
"original": {
"owner": "antifuchs",
"repo": "nix-flake-tests",
"type": "github"
}
},
"nix-log": {
"inputs": {
"nix-flake-tests": "nix-flake-tests",
"nixify": "nixify_2",
"nixlib": "nixlib_2"
},
"locked": {
"lastModified": 1681933283,
"narHash": "sha256-phDsQdaoUEI4DUTErR6Tz7lS0y3kXvDwwbqtxpzd0eo=",
"owner": "rvolosatovs",
"repo": "nix-log",
"rev": "833d31e3c1a677eac81ba87e777afa5076071d66",
"type": "github"
},
"original": {
"owner": "rvolosatovs",
"repo": "nix-log",
"type": "github"
}
},
"nix-log_2": {
"inputs": {
"nix-flake-tests": "nix-flake-tests_2",
"nixify": [
"wasmcloud",
"wit-deps",
"nixify"
],
"nixlib": [
"wasmcloud",
"wit-deps",
"nixlib"
]
},
"locked": {
"lastModified": 1681933283,
"narHash": "sha256-phDsQdaoUEI4DUTErR6Tz7lS0y3kXvDwwbqtxpzd0eo=",
"owner": "rvolosatovs",
"repo": "nix-log",
"rev": "833d31e3c1a677eac81ba87e777afa5076071d66",
"type": "github"
},
"original": {
"owner": "rvolosatovs",
"repo": "nix-log",
"type": "github"
}
},
"nixify": {
"inputs": {
"advisory-db": "advisory-db_2",
"crane": "crane_2",
"fenix": "fenix_2",
"flake-utils": "flake-utils_2",
"macos-sdk": "macos-sdk",
"nix-filter": "nix-filter",
"nix-log": "nix-log",
"nixlib": [
"wasmcloud",
"nixlib"
],
"nixpkgs-darwin": "nixpkgs-darwin",
"nixpkgs-nixos": "nixpkgs-nixos",
"rust-overlay": "rust-overlay_2"
},
"locked": {
"lastModified": 1731068753,
"narHash": "sha256-6H+vYAYl/koFsiBEM4WHZhOoOQ2Hfzd+MtcxFfAOOtw=",
"owner": "rvolosatovs",
"repo": "nixify",
"rev": "7b83953ebfb22ba1f623ac06312aebee81f2182e",
"type": "github"
},
"original": {
"owner": "rvolosatovs",
"repo": "nixify",
"type": "github"
}
},
"nixify_2": {
"inputs": {
"crane": "crane_3",
"fenix": "fenix_3",
"flake-utils": "flake-utils_3",
"nix-filter": "nix-filter_2",
"nixlib": "nixlib",
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1679748566,
"narHash": "sha256-yA4yIJjNCOLoUh0py9S3SywwbPnd/6NPYbXad+JeOl0=",
"owner": "rvolosatovs",
"repo": "nixify",
"rev": "80e823959511a42dfec4409fef406a14ae8240f3",
"type": "github"
},
"original": {
"owner": "rvolosatovs",
"repo": "nixify",
"type": "github"
}
},
"nixlib": {
"locked": {
"lastModified": 1679187309,
"narHash": "sha256-H8udmkg5wppL11d/05MMzOMryiYvc403axjDNZy1/TQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "44214417fe4595438b31bdb9469be92536a61455",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixlib_2": {
"locked": {
"lastModified": 1679791877,
"narHash": "sha256-tTV1Mf0hPWIMtqyU16Kd2JUBDWvfHlDC9pF57vcbgpQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "cc060ddbf652a532b54057081d5abd6144d01971",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixlib_3": {
"locked": {
"lastModified": 1731200463,
"narHash": "sha256-qDaAweJjdFbVExqs8aG27urUgcgKufkIngHW3Rzustg=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "e04234d263750db01c78a412690363dc2226e68a",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1738163270,
"narHash": "sha256-B/7Y1v4y+msFFBW1JAdFjNvVthvNdJKiN6EGRPnqfno=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "59e618d90c065f55ae48446f307e8c09565d5ab0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-darwin": {
"locked": {
"lastModified": 1730891215,
"narHash": "sha256-i85DPrhDuvzgvIWCpJlbfM2UFtNYbapo20MtQXsvay4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c128e44a249d6180740d0a979b6480d5b795c013",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-24.05-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-nixos": {
"locked": {
"lastModified": 1730883749,
"narHash": "sha256-mwrFF0vElHJP8X3pFCByJR365Q2463ATp2qGIrDUdlE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "dba414932936fde69f0606b4f1d87c5bc0003ede",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1679577639,
"narHash": "sha256-7u7bsNP0ApBnLgsHVROQ5ytoMqustmMVMgtaFS/P7EU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "8f1bcd72727c5d4cd775545595d068be410f2a7e",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-22.11-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"advisory-db": "advisory-db",
"crane": "crane",
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"wasmcloud": "wasmcloud"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1730989300,
"narHash": "sha256-ZWSta9893f/uF5PoRFn/BSUAxF4dKW+TIbdA6rZoGBg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "1042a8c22c348491a4bade4f664430b03d6f5b5c",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-analyzer-src_2": {
"flake": false,
"locked": {
"lastModified": 1679520343,
"narHash": "sha256-AJGSGWRfoKWD5IVTu1wEsR990wHbX0kIaolPqNMEh0c=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "eb791f31e688ae00908eb75d4c704ef60c430a92",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"wasmcloud",
"nixify",
"nix-log",
"nixify",
"flake-utils"
],
"nixpkgs": [
"wasmcloud",
"nixify",
"nix-log",
"nixify",
"nixpkgs"
]
},
"locked": {
"lastModified": 1679537973,
"narHash": "sha256-R6borgcKeyMIjjPeeYsfo+mT8UdS+OwwbhhStdCfEjg=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "fbc7ae3f14d32e78c0e8d7865f865cc28a46b232",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"rust-overlay_2": {
"inputs": {
"nixpkgs": [
"wasmcloud",
"nixify",
"nixpkgs-nixos"
]
},
"locked": {
"lastModified": 1731032894,
"narHash": "sha256-dQSyYPmrQiPr+PGEd+K8038rubFGz7G/dNXVeaGWE0w=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "d52f2a4c103a0acf09ded857b9e2519ae2360e59",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"wasmcloud": {
"inputs": {
"nixify": "nixify",
"nixlib": "nixlib_3",
"wit-deps": "wit-deps"
},
"locked": {
"lastModified": 1731409523,
"narHash": "sha256-Q/BnuJaMyJfY+p9VpdyBWtRjEo4TdRvFMMhfdDFj6cU=",
"owner": "wasmCloud",
"repo": "wasmCloud",
"rev": "579455058513b907c7df4a4ec13728f83c6b782b",
"type": "github"
},
"original": {
"owner": "wasmCloud",
"ref": "wash-cli-v0.37.0",
"repo": "wasmCloud",
"type": "github"
}
},
"wit-deps": {
"inputs": {
"nix-log": "nix-log_2",
"nixify": [
"wasmcloud",
"nixify"
],
"nixlib": [
"wasmcloud",
"nixlib"
]
},
"locked": {
"lastModified": 1727963723,
"narHash": "sha256-urAGMGMH5ousEeVTZ5AaLPfowXaYQoISNXiutV00iQo=",
"owner": "bytecodealliance",
"repo": "wit-deps",
"rev": "eb7c84564acfe13a4197bb15052fd2e2b3d29775",
"type": "github"
},
"original": {
"owner": "bytecodealliance",
"ref": "v0.4.0",
"repo": "wit-deps",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

264
flake.nix Normal file
View File

@ -0,0 +1,264 @@
{
nixConfig.extra-substituters =
[ "https://wasmcloud.cachix.org" "https://crane.cachix.org" ];
nixConfig.extra-trusted-public-keys = [
"wasmcloud.cachix.org-1:9gRBzsKh+x2HbVVspreFg/6iFRiD4aOcUQfXVDl3hiM="
"crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk="
];
description = "A flake for building and running wadm";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/release-24.11";
crane.url = "github:ipetkov/crane";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.rust-analyzer-src.follows = "";
};
flake-utils.url = "github:numtide/flake-utils";
advisory-db = {
url = "github:rustsec/advisory-db";
flake = false;
};
# The wash CLI flag is always after the latest host release tag we want
wasmcloud.url = "github:wasmCloud/wasmCloud/wash-cli-v0.37.0";
};
outputs =
{ self, nixpkgs, crane, fenix, flake-utils, advisory-db, wasmcloud, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) lib;
craneLib = crane.mkLib pkgs;
src = craneLib.cleanCargoSource ./.;
# Common arguments can be set here to avoid repeating them later
commonArgs = {
inherit src;
strictDeps = true;
buildInputs = [
# Add additional build inputs here
] ++ lib.optionals pkgs.stdenv.isDarwin [
# Additional darwin specific inputs can be set here if needed
];
# Additional environment variables can be set directly here if needed
# MY_CUSTOM_VAR = "some value";
};
craneLibLLvmTools = craneLib.overrideToolchain
(fenix.packages.${system}.complete.withComponents [
"cargo"
"llvm-tools"
"rustc"
]);
# Get the lock file for filtering
rawLockFile = builtins.fromTOML (builtins.readFile ./Cargo.lock);
# Filter out the workspace members
filteredLockFile = rawLockFile // {
package = builtins.filter (x: !lib.strings.hasPrefix "wadm" x.name)
rawLockFile.package;
};
cargoVendorDir =
craneLib.vendorCargoDeps { cargoLockParsed = filteredLockFile; };
cargoLock = craneLib.writeTOML "Cargo.lock" filteredLockFile;
# Build *just* the cargo dependencies (of the entire workspace), but we don't want to build
# any of the other things in the crate to avoid rebuilding things in the dependencies when
# we change workspace crate dependencies
cargoArtifacts = let
commonArgs' = removeAttrs commonArgs [ "src" ];
# Get the manifest file for filtering
rawManifestFile = builtins.fromTOML (builtins.readFile ./Cargo.toml);
# Filter out the workspace members from manifest
filteredManifestFile = with lib;
let
filterWadmAttrs =
filterAttrs (name: _: !strings.hasPrefix "wadm" name);
workspace = removeAttrs rawManifestFile.workspace [ "members" ];
in rawManifestFile // {
workspace = workspace // {
dependencies = filterWadmAttrs workspace.dependencies;
package = workspace.package // {
# pin version to avoid rebuilds on bumps
version = "0.0.0";
};
};
dependencies = filterWadmAttrs rawManifestFile.dependencies;
dev-dependencies =
filterWadmAttrs rawManifestFile.dev-dependencies;
build-dependencies =
filterWadmAttrs rawManifestFile.build-dependencies;
};
cargoToml = craneLib.writeTOML "Cargo.toml" filteredManifestFile;
dummySrc = craneLib.mkDummySrc {
src = pkgs.runCommand "wadm-dummy-src" { } ''
mkdir -p $out
cp --recursive --no-preserve=mode,ownership ${src}/. -t $out
cp ${cargoToml} $out/Cargo.toml
'';
};
args = commonArgs' // {
inherit cargoLock cargoToml cargoVendorDir dummySrc;
cargoExtraArgs = ""; # disable `--locked` passed by default by crane
};
in craneLib.buildDepsOnly args;
individualCrateArgs = commonArgs // {
inherit (craneLib.crateNameFromCargoToml { inherit src; }) version;
# TODO(thomastaylor312) We run unit tests here and e2e tests externally. The nextest step
# wasn't letting me pass in the fileset
doCheck = true;
};
fileSetForCrate = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./Cargo.toml
./Cargo.lock
./tests
./oam
(craneLib.fileset.commonCargoSources ./crates/wadm)
(craneLib.fileset.commonCargoSources ./crates/wadm-client)
(craneLib.fileset.commonCargoSources ./crates/wadm-types)
];
};
# Build the top-level crates of the workspace as individual derivations.
# This allows consumers to only depend on (and build) only what they need.
# Though it is possible to build the entire workspace as a single derivation,
# so this is left up to you on how to organize things
#
# Note that the cargo workspace must define `workspace.members` using wildcards,
# otherwise, omitting a crate (like we do below) will result in errors since
# cargo won't be able to find the sources for all members.
# TODO(thomastaylor312) I tried using `doInstallCargoArtifacts` and passing in things to the
# next derivations as the `cargoArtifacts`, but that ended up always building things twice
# rather than caching. We should look into it more and see if there's a way to make it work.
wadm-lib = craneLib.cargoBuild (individualCrateArgs // {
inherit cargoArtifacts;
pname = "wadm";
cargoExtraArgs = "-p wadm";
src = fileSetForCrate;
});
wadm = craneLib.buildPackage (individualCrateArgs // {
inherit cargoArtifacts;
pname = "wadm-cli";
cargoExtraArgs = "--bin wadm";
src = fileSetForCrate;
});
wadm-client = craneLib.cargoBuild (individualCrateArgs // {
inherit cargoArtifacts;
pname = "wadm-client";
cargoExtraArgs = "-p wadm-client";
src = fileSetForCrate;
});
wadm-types = craneLib.cargoBuild (individualCrateArgs // {
inherit cargoArtifacts;
pname = "wadm-types";
cargoExtraArgs = "-p wadm-types";
src = fileSetForCrate;
});
in {
checks = {
# Build the crates as part of `nix flake check` for convenience
inherit wadm wadm-client wadm-types;
# Run clippy (and deny all warnings) on the workspace source,
# again, reusing the dependency artifacts from above.
#
# Note that this is done as a separate derivation so that
# we can block the CI if there are issues here, but not
# prevent downstream consumers from building our crate by itself.
workspace-clippy = craneLib.cargoClippy (commonArgs // {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
});
workspace-doc =
craneLib.cargoDoc (commonArgs // { inherit cargoArtifacts; });
# Check formatting
workspace-fmt = craneLib.cargoFmt { inherit src; };
# Audit dependencies
workspace-audit = craneLib.cargoAudit { inherit src advisory-db; };
# Audit licenses
# my-workspace-deny = craneLib.cargoDeny {
# inherit src;
# };
# TODO: the wadm e2e tests use docker compose and things like `wash up` to test things
# (which accesses network currently). We would need to fix those tests to do something
# else to work properly. The low hanging fruit here would be to use the built artifact
# in the e2e tests so we can output those binaries from the nix build and then just
# run the tests from a separate repo. We could also do something like outputting the
# prebuilt artifacts out into the current directory to save on build time. But that is
# for later us to figure out
runE2ETests = pkgs.runCommand "e2e-tests" {
nativeBuildInputs = with pkgs;
[
nats-server
# wasmcloud.wasmcloud
];
} ''
touch $out
'';
};
packages = {
inherit wadm wadm-client wadm-types wadm-lib;
default = wadm;
} // lib.optionalAttrs (!pkgs.stdenv.isDarwin) {
workspace-llvm-coverage = craneLibLLvmTools.cargoLlvmCov
(commonArgs // { inherit cargoArtifacts; });
};
apps = {
wadm = flake-utils.lib.mkApp { drv = wadm; };
default = flake-utils.lib.mkApp { drv = wadm; };
};
devShells.default = craneLib.devShell {
# Inherit inputs from checks.
checks = self.checks.${system};
RUST_SRC_PATH =
"${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
# Extra inputs can be added here; cargo and rustc are provided by default.
packages = [
pkgs.nats-server
pkgs.natscli
pkgs.docker
pkgs.git
wasmcloud.outputs.packages.${system}.default
];
};
});
}

View File

@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Manifest",
"description": "An OAM manifest",
"description": "Manifest file based on the Open Application Model (OAM) specification for declaratively managing wasmCloud applications",
"type": "object",
"required": [
"apiVersion",
@ -39,10 +39,18 @@
"definitions": {
"CapabilityProperties": {
"type": "object",
"required": [
"image"
],
"properties": {
"application": {
"description": "Information to locate a component within a shared application. Cannot be specified if the image is specified.",
"anyOf": [
{
"$ref": "#/definitions/SharedApplicationComponentProperties"
},
{
"type": "null"
}
]
},
"config": {
"description": "Named configuration to pass to the provider. The merged set of configuration will be passed to the provider at runtime using the provider SDK's `init()` function.",
"type": "array",
@ -58,8 +66,11 @@
]
},
"image": {
"description": "The image reference to use",
"type": "string"
"description": "The image reference to use. Required unless the component is a shared component that is defined in another shared application.",
"type": [
"string",
"null"
]
},
"secrets": {
"description": "Named secret references to pass to the t. The provider will be able to retrieve these values at runtime using `wasmcloud:secrets/store`.",
@ -134,10 +145,18 @@
},
"ComponentProperties": {
"type": "object",
"required": [
"image"
],
"properties": {
"application": {
"description": "Information to locate a component within a shared application. Cannot be specified if the image is specified.",
"anyOf": [
{
"$ref": "#/definitions/SharedApplicationComponentProperties"
},
{
"type": "null"
}
]
},
"config": {
"description": "Named configuration to pass to the component. The component will be able to retrieve these values at runtime using `wasi:runtime/config.`",
"type": "array",
@ -153,8 +172,11 @@
]
},
"image": {
"description": "The image reference to use",
"type": "string"
"description": "The image reference to use. Required unless the component is a shared component that is defined in another shared application.",
"type": [
"string",
"null"
]
},
"secrets": {
"description": "Named secret references to pass to the component. The component will be able to retrieve these values at runtime using `wasmcloud:secrets/store`.",
@ -280,7 +302,8 @@
"$ref": "#/definitions/ConfigProperty"
}
}
}
},
"additionalProperties": false
},
"Metadata": {
"description": "The metadata describing the manifest",
@ -340,15 +363,15 @@
"type": "object",
"required": [
"name",
"source"
"properties"
],
"properties": {
"name": {
"description": "The name of the secret. This is used by a reference by the component or capability to get the secret value as a resource.",
"type": "string"
},
"source": {
"description": "The source of the secret. This indicates how to retrieve the secret value from a secrets backend and which backend to actually query.",
"properties": {
"description": "The properties of the secret that indicate how to retrieve the secret value from a secrets backend and which backend to actually query.",
"allOf": [
{
"$ref": "#/definitions/SecretSourceProperty"
@ -364,6 +387,13 @@
"policy"
],
"properties": {
"field": {
"description": "The field to use for retrieving the secret from the backend. This is optional and can be used to retrieve a specific field from a secret.",
"type": [
"string",
"null"
]
},
"key": {
"description": "The key to use for retrieving the secret from the backend.",
"type": "string"
@ -381,6 +411,23 @@
}
}
},
"SharedApplicationComponentProperties": {
"type": "object",
"required": [
"component",
"name"
],
"properties": {
"component": {
"description": "The name of the component in the shared application",
"type": "string"
},
"name": {
"description": "The name of the shared application",
"type": "string"
}
}
},
"Specification": {
"description": "A representation of an OAM specification",
"type": "object",

View File

@ -16,9 +16,13 @@ The following is a list of the `traits` wasmCloud has added via customization to
- `spreadscaler` - Defines the spread of instances of a particular entity across multiple hosts with affinity requirements
- `link` - A link definition that describes a link between a component and a capability provider or a component and another component
## JSON Schema
A JSON schema is automatically generated from our Rust structures and is at the root of the repository: [oam.schema.json](../oam.schema.json). You can regenerate the `oam.schema.json` file by running `cargo run --bin wadm-schema`.
## Example Application YAML
The following is an example YAML file describing an ALC application
The following is an example YAML file describing an application
```yaml
apiVersion: core.oam.dev/v1beta1
@ -26,8 +30,7 @@ kind: Application
metadata:
name: my-example-app
annotations:
version: v0.0.1
description: 'This is my app'
description: 'This is my app revision 2'
spec:
components:
- name: userinfo
@ -47,25 +50,28 @@ spec:
requirements:
zone: us-west-1
weight: 20
- type: linkdef
properties:
target: webcap
values:
port: '8080'
- name: webcap
type: capability
properties:
contract: wasmcloud:httpserver
image: wasmcloud.azurecr.io/httpserver:0.13.1
link_name: default
traits:
- type: link
properties:
target:
name: userinfo
config: []
namespace: wasi
package: http
interfaces:
- incoming-handler
source:
config: []
- name: ledblinky
type: capability
properties:
image: wasmcloud.azurecr.io/ledblinky:0.0.1
contract: wasmcloud:blinkenlights
# default link name is "default"
traits:
- type: spreadscaler
properties:
@ -74,5 +80,5 @@ spec:
- name: haslights
requirements:
ledenabled: 'true'
# default weight is 100
# default weight is 100
```

View File

@ -19,7 +19,7 @@ spec:
- name: webcap
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
image: ghcr.io/wasmcloud/http-server:0.23.0
# You can pass any config data you'd like sent to your provider as a string->string map
config:
- name: provider_config

View File

@ -21,7 +21,7 @@ spec:
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
image: ghcr.io/wasmcloud/http-server:0.23.0
traits:
# Link the HTTP server and set it to listen on the local machine's port 8080
- type: link

View File

@ -37,7 +37,7 @@ spec:
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
image: ghcr.io/wasmcloud/http-server:0.23.0
traits:
# Compose with component to handle wasi:http calls
- type: link

View File

@ -1,411 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://oam.dev/v1/oam.application_configuration.schema.json",
"title": "Manifest",
"description": "A JSON Schema to validate wasmCloud Application Deployment Manager (WADM) manifests",
"type": "object",
"properties": {
"apiVersion": {
"type": "string",
"description": "The specific version of the Open Application Model specification in use"
},
"kind": {
"type": "string",
"description": "The entity type being described in the manifest"
},
"metadata": {
"type": "object",
"description": "Application configuration metadata.",
"properties": {
"name": {
"type": "string"
},
"annotations": {
"type": "object",
"description": "A set of string key/value pairs used as arbitrary annotations on this application configuration.",
"properties": {
"description": {
"type": "string"
}
},
"additionalProperties": {
"type": "string"
}
}
}
},
"spec": {
"type": "object",
"description": "Configuration attributes for various items in the lattice",
"$ref": "#/definitions/manifestSpec"
}
},
"required": [
"apiVersion",
"kind",
"metadata",
"spec"
],
"additionalProperties": false,
"definitions": {
"manifestSpec": {
"type": "object",
"properties": {
"components": {
"type": "array",
"description": "Component instance definitions.",
"items": {
"type": "object",
"anyOf": [
{
"$ref": "#/definitions/wasmComponent"
},
{
"$ref": "#/definitions/providerComponent"
}
]
}
}
},
"required": [
"components"
],
"additionalProperties": false
},
"opconfigVariable": {
"type": "object",
"description": "The Variables section defines variables that may be used elsewhere in the application configuration. The variable section provides a way for an application operator to specify common values that can be substituted into multiple other locations in this configuration (using the [fromVariable(VARNAME)] syntax).",
"properties": {
"name": {
"type": "string",
"description": "The parameter's name. Must be unique per configuration.",
"$comment": "Some systems have upper bounds for name length. Do we limit here?",
"maxLength": 128
},
"value": {
"type": "string",
"description": "The scalar value."
}
},
"required": [
"name",
"value"
],
"additionalProperties": false
},
"applicationScope": {
"type": "object",
"description": "The scope section defines application scopes that will be created with this application configuration.",
"properties": {
"name": {
"type": "string",
"description": "The name of the application scope. Must be unique to the deployment environment."
},
"type": {
"type": "string",
"description": "The fully-qualified GROUP/VERSION.KIND name of the application scope."
},
"properties": {
"type": "object",
"description": "The properties attached to this scope.",
"$ref": "#/definitions/propertiesObject"
}
},
"required": [
"name",
"type"
],
"additionalProperties": false
},
"wasmComponent": {
"type": "object",
"description": "This section defines the instances of components to create with this application configuration.",
"properties": {
"name": {
"type": "string",
"description": "The name of the component to create an instance of."
},
"type": {
"description": "The type of instance : component.",
"anyOf": [
{
"const": "component"
},
{
"const": "actor",
"$comment": "Deprecated: use 'component' instead"
}
]
},
"properties": {
"type": "object",
"description": "Overrides of parameters that are exposed by the application scope type defined in 'type'.",
"$ref": "#/definitions/componentProperties"
},
"traits": {
"type": "array",
"description": "Specifies the traits to attach to this component instance.",
"items": {
"$ref": "#/definitions/trait"
}
}
},
"required": [
"name",
"type",
"properties"
],
"additionalProperties": true
},
"providerComponent": {
"type": "object",
"description": "This section defines the instances of providers to create with this application configuration.",
"properties": {
"name": {
"type": "string",
"description": "The name of the provider to create an instance of."
},
"type": {
"description": "The type of instance: capability.",
"const": "capability"
},
"properties": {
"type": "object",
"description": "Overrides of parameters that are exposed by the application scope type defined in 'type'.",
"$ref": "#/definitions/providerProperties"
},
"traits": {
"type": "array",
"description": "Specifies the traits to attach to this component instance.",
"items": {
"$ref": "#/definitions/trait"
}
}
},
"required": [
"name",
"type",
"properties"
],
"additionalProperties": true
},
"componentProperties": {
"type": "object",
"description": "Values supplied to parameters that are used to override the parameters exposed by other types.",
"properties": {
"image": {
"type": "string",
"description": "The image reference to use for the component.",
"$comment": "Some systems have upper bounds for name length. Do we limit here?",
"maxLength": 512
},
"id": {
"type": "string",
"description": "The component identifier to use for the component. Will be autogenerated if not supplied.",
"maxLength": 64
},
"config": {
"type": "array",
"items": {
"$ref": "#/definitions/configProperty"
},
"default": [],
"description": "Configuration properties for the provider"
}
},
"required": [
"image"
],
"additionalProperties": false
},
"providerProperties": {
"type": "object",
"description": "Values supplied to parameters that are used to override the parameters exposed by other types.",
"properties": {
"image": {
"type": "string",
"description": "The image reference to use for the provider.",
"$comment": "Some systems have upper bounds for name length. Do we limit here?",
"maxLength": 512
},
"id": {
"type": "string",
"description": "The component identifier to use for the provider.",
"maxLength": 64
},
"config": {
"type": "array",
"items": {
"$ref": "#/definitions/configProperty"
},
"default": [],
"description": "Configuration properties for the provider"
}
},
"required": [
"image"
],
"additionalProperties": false
},
"trait": {
"type": "object",
"description": "The trait section defines traits that will be used in a component instance.",
"properties": {
"type": {
"type": "string",
"description": "The trait type for the instance, whether spreadscaler or link"
},
"properties": {
"type": "object",
"description": "Overrides of parameters that are exposed by the trait type defined in 'type'.",
"anyOf": [
{
"$ref": "#/definitions/linkProperties"
},
{
"$ref": "#/definitions/spreadscalerProperties"
}
]
}
},
"required": [
"type",
"properties"
],
"additionalProperties": false
},
"configProperty": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"name"
],
"additionalProperties": false
},
"linkProperties": {
"target": {
"type": "string",
"description": "The target this link applies to. This should be the name of a component in the manifest"
},
"namespace": {
"type": "string",
"description": "WIT namespace for the link"
},
"package": {
"type": "string",
"description": "WIT package for the link"
},
"interfaces": {
"type": "array",
"items": {
"type": "string"
},
"description": "WIT interfaces for the link"
},
"source_config": {
"type": "array",
"items": {
"$ref": "#/definitions/configProperty"
},
"default": [],
"description": "Configuration properties for the source of the link"
},
"target_config": {
"type": "array",
"items": {
"$ref": "#/definitions/configProperty"
},
"default": [],
"description": "Configuration properties for the target of the link"
},
"name": {
"type": "string",
"description": "The name of this link",
"default": null
},
"required": [
"target",
"namespace",
"package",
"interfaces"
]
},
"spreadscalerProperties": {
"type": "object",
"description": "A properties object (for spreadscaler configuration) is an object whose structure is determined by the spreadscaler property schema. It may be a simple value, or it may be a complex object.",
"properties": {
"instances": {
"anyOf": [
{
"type": "integer",
"title": "instances"
},
{
"type": "integer",
"title": "replicas"
}
]
},
"spread": {
"type": "array",
"items": {
"type": "object",
"description": "A spread object for spreading instances.",
"properties": {
"name": {
"type": "string"
},
"requirements": {
"additionalProperties": {
"type": "string"
}
},
"weight": {
"type": "integer"
}
},
"required": [
"name",
"requirements"
]
}
}
},
"oneOf": [
{
"required": [
"instances"
]
},
{
"required": [
"replicas"
]
}
]
},
"propertiesObject": {
"anyOf": [
{
"type": "object",
"description": "A properties object (for trait and scope configuration) is an object whose structure is determined by the trait or scope property schema. It may be a simple value, or it may be a complex object.",
"additionalProperties": true
},
{
"type": "string",
"description": "A properties object (for trait and scope configuration) is an object whose structure is determined by the trait or scope property schema. It may be a simple value, or it may be a complex object."
}
]
}
}
}

3
rust-toolchain.toml Normal file
View File

@ -0,0 +1,3 @@
[toolchain]
channel = "stable"
components = ["clippy", "rust-src", "rustfmt"]

View File

@ -1,9 +1,10 @@
use std::io::IsTerminal;
use opentelemetry::sdk::{
trace::{IdGenerator, Sampler},
Resource,
};
use opentelemetry_otlp::{Protocol, WithExportConfig};
use std::io::IsTerminal;
use tracing::{Event as TracingEvent, Subscriber};
use tracing_subscriber::fmt::{
format::{Format, Full, Json, JsonFields, Writer},

View File

@ -1,448 +1,40 @@
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use async_nats::jetstream::{stream::Stream, Context};
use anyhow::Context as _;
use clap::Parser;
use tokio::sync::Semaphore;
use tracing::log::debug;
use wadm_types::api::DEFAULT_WADM_TOPIC_PREFIX;
use wadm::{config::WadmConfig, start_wadm};
use wadm::{
consumers::{
manager::{ConsumerManager, WorkerCreator},
*,
},
nats_utils::LatticeIdParser,
scaler::manager::{ScalerManager, WADM_NOTIFY_PREFIX},
server::{ManifestNotifier, Server},
storage::{nats_kv::NatsKvStore, reaper::Reaper},
workers::{CommandPublisher, CommandWorker, EventWorker, StatusPublisher},
DEFAULT_COMMANDS_TOPIC, DEFAULT_EVENTS_TOPIC, DEFAULT_MULTITENANT_EVENTS_TOPIC,
DEFAULT_STATUS_TOPIC, DEFAULT_WADM_EVENTS_TOPIC, DEFAULT_WADM_EVENT_CONSUMER_TOPIC,
};
mod connections;
mod logging;
mod nats;
mod observer;
use connections::ControlClientConstructor;
const WADM_EVENT_STREAM_NAME: &str = "wadm_events";
const WADM_EVENT_CONSUMER_STREAM_NAME: &str = "wadm_event_consumer";
const COMMAND_STREAM_NAME: &str = "wadm_commands";
const STATUS_STREAM_NAME: &str = "wadm_status";
const NOTIFY_STREAM_NAME: &str = "wadm_notify";
const WASMBUS_EVENT_STREAM_NAME: &str = "wasmbus_events";
#[derive(Parser, Debug)]
#[command(name = clap::crate_name!(), version = clap::crate_version!(), about = "wasmCloud Application Deployment Manager", long_about = None)]
struct Args {
/// The ID for this wadm process. Defaults to a random UUIDv4 if none is provided. This is used
/// to help with debugging when identifying which process is doing the work
#[arg(short = 'i', long = "host-id", env = "WADM_HOST_ID")]
host_id: Option<String>,
/// Whether or not to use structured log output (as JSON)
#[arg(
short = 'l',
long = "structured-logging",
default_value = "false",
env = "WADM_STRUCTURED_LOGGING"
)]
structured_logging: bool,
/// Whether or not to enable opentelemetry tracing
#[arg(
short = 't',
long = "tracing",
default_value = "false",
env = "WADM_TRACING_ENABLED"
)]
tracing_enabled: bool,
/// The endpoint to use for tracing. Setting this flag enables tracing, even if --tracing is set
/// to false. Defaults to http://localhost:4318/v1/traces if not set and tracing is enabled
#[arg(short = 'e', long = "tracing-endpoint", env = "WADM_TRACING_ENDPOINT")]
tracing_endpoint: Option<String>,
/// The NATS JetStream domain to connect to
#[arg(short = 'd', env = "WADM_JETSTREAM_DOMAIN")]
domain: Option<String>,
/// (Advanced) Tweak the maximum number of jobs to run for handling events and commands. Be
/// careful how you use this as it can affect performance
#[arg(short = 'j', long = "max-jobs", env = "WADM_MAX_JOBS")]
max_jobs: Option<usize>,
/// The URL of the nats server you want to connect to
#[arg(
short = 's',
long = "nats-server",
env = "WADM_NATS_SERVER",
default_value = "127.0.0.1:4222"
)]
nats_server: String,
/// Use the specified nkey file or seed literal for authentication. Must be used in conjunction with --nats-jwt
#[arg(
long = "nats-seed",
env = "WADM_NATS_NKEY",
conflicts_with = "nats_creds",
requires = "nats_jwt"
)]
nats_seed: Option<String>,
/// Use the specified jwt file or literal for authentication. Must be used in conjunction with --nats-nkey
#[arg(
long = "nats-jwt",
env = "WADM_NATS_JWT",
conflicts_with = "nats_creds",
requires = "nats_seed"
)]
nats_jwt: Option<String>,
/// (Optional) NATS credential file to use when authenticating
#[arg(
long = "nats-creds-file",
env = "WADM_NATS_CREDS_FILE",
conflicts_with_all = ["nats_seed", "nats_jwt"],
)]
nats_creds: Option<PathBuf>,
/// (Optional) NATS TLS certificate file to use when authenticating
#[arg(long = "nats-tls-ca-file", env = "WADM_NATS_TLS_CA_FILE")]
nats_tls_ca_file: Option<PathBuf>,
/// Name of the bucket used for storage of lattice state
#[arg(
long = "state-bucket-name",
env = "WADM_STATE_BUCKET_NAME",
default_value = "wadm_state"
)]
state_bucket: String,
/// The amount of time in seconds to give for hosts to fail to heartbeat and be removed from the
/// store. By default, this is 120s because it is 4x the host heartbeat interval
#[arg(
long = "cleanup-interval",
env = "WADM_CLEANUP_INTERVAL",
default_value = "120"
)]
cleanup_interval: u64,
/// The API topic prefix to use. This is an advanced setting that should only be used if you
/// know what you are doing
#[arg(
long = "api-prefix",
env = "WADM_API_PREFIX",
default_value = DEFAULT_WADM_TOPIC_PREFIX
)]
api_prefix: String,
/// This prefix to used for the internal streams. When running in a multitenant environment,
/// clients share the same JS domain (since messages need to come from lattices).
/// Setting a stream prefix makes it possible to have a separate stream for different wadms running in a multitenant environment.
/// This is an advanced setting that should only be used if you know what you are doing.
#[arg(long = "stream-prefix", env = "WADM_STREAM_PREFIX")]
stream_prefix: Option<String>,
/// Name of the bucket used for storage of manifests
#[arg(
long = "manifest-bucket-name",
env = "WADM_MANIFEST_BUCKET_NAME",
default_value = "wadm_manifests"
)]
manifest_bucket: String,
/// Run wadm in multitenant mode. This is for advanced multitenant use cases with segmented NATS
/// account traffic and not simple cases where all lattices use credentials from the same
/// account. See the deployment guide for more information
#[arg(long = "multitenant", env = "WADM_MULTITENANT", hide = true)]
multitenant: bool,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let args = WadmConfig::parse();
logging::configure_tracing(
args.structured_logging,
args.tracing_enabled,
args.tracing_endpoint,
args.tracing_endpoint.clone(),
);
// Build storage adapter for lattice state (on by default)
let (client, context) = nats::get_client_and_context(
args.nats_server.clone(),
args.domain.clone(),
args.nats_seed.clone(),
args.nats_jwt.clone(),
args.nats_creds.clone(),
args.nats_tls_ca_file.clone(),
)
.await?;
// TODO: We will probably need to set up all the flags (like lattice prefix and topic prefix) down the line
let connection_pool = ControlClientConstructor::new(client.clone(), None);
let trimmer: &[_] = &['.', '>', '*'];
let store = nats::ensure_kv_bucket(&context, args.state_bucket, 1).await?;
let state_storage = NatsKvStore::new(store);
let manifest_storage = nats::ensure_kv_bucket(&context, args.manifest_bucket, 1).await?;
let internal_stream_name = |stream_name: &str| -> String {
match args.stream_prefix.clone() {
Some(stream_prefix) => {
format!(
"{}.{}",
stream_prefix.trim_end_matches(trimmer),
stream_name
)
}
None => stream_name.to_string(),
}
};
debug!("Ensuring wadm event stream");
let event_stream = nats::ensure_limits_stream(
&context,
internal_stream_name(WADM_EVENT_STREAM_NAME),
vec![DEFAULT_WADM_EVENTS_TOPIC.to_owned()],
Some(
"A stream that stores all events coming in on the wadm.evt subject in a cluster"
.to_string(),
),
)
.await?;
debug!("Ensuring command stream");
let command_stream = nats::ensure_stream(
&context,
internal_stream_name(COMMAND_STREAM_NAME),
vec![DEFAULT_COMMANDS_TOPIC.to_owned()],
Some("A stream that stores all commands for wadm".to_string()),
)
.await?;
let status_stream = nats::ensure_status_stream(
&context,
internal_stream_name(STATUS_STREAM_NAME),
vec![DEFAULT_STATUS_TOPIC.to_owned()],
)
.await?;
debug!("Ensuring wasmbus event stream");
// Remove the previous wadm_(multitenant)_mirror streams so that they don't
// prevent us from creating the new wasmbus_(multitenant)_events stream
// TODO(joonas): Remove this some time in the future once we're confident
// enough that there are no more wadm_(multitenant)_mirror streams around.
for mirror_stream_name in &["wadm_mirror", "wadm_multitenant_mirror"] {
if (context.get_stream(mirror_stream_name).await).is_ok() {
context.delete_stream(mirror_stream_name).await?;
}
}
let wasmbus_event_subjects = match args.multitenant {
true => vec![DEFAULT_MULTITENANT_EVENTS_TOPIC.to_owned()],
false => vec![DEFAULT_EVENTS_TOPIC.to_owned()],
};
let wasmbus_event_stream = nats::ensure_limits_stream(
&context,
WASMBUS_EVENT_STREAM_NAME.to_string(),
wasmbus_event_subjects.clone(),
Some(
"A stream that stores all events coming in on the wasmbus.evt subject in a cluster"
.to_string(),
),
)
.await?;
debug!("Ensuring notify stream");
let notify_stream = nats::ensure_notify_stream(
&context,
NOTIFY_STREAM_NAME.to_owned(),
vec![format!("{WADM_NOTIFY_PREFIX}.*")],
)
.await?;
debug!("Ensuring event consumer stream");
let event_consumer_stream = nats::ensure_event_consumer_stream(
&context,
WADM_EVENT_CONSUMER_STREAM_NAME.to_owned(),
DEFAULT_WADM_EVENT_CONSUMER_TOPIC.to_owned(),
vec![&wasmbus_event_stream, &event_stream],
Some(
"A stream that sources from wadm_events and wasmbus_events for wadm event consumer's use"
.to_string(),
),
)
.await?;
debug!("Creating event consumer manager");
let permit_pool = Arc::new(Semaphore::new(
args.max_jobs.unwrap_or(Semaphore::MAX_PERMITS),
));
let event_worker_creator = EventWorkerCreator {
state_store: state_storage.clone(),
manifest_store: manifest_storage.clone(),
pool: connection_pool.clone(),
command_topic_prefix: DEFAULT_COMMANDS_TOPIC.trim_matches(trimmer).to_owned(),
publisher: context.clone(),
notify_stream,
status_stream: status_stream.clone(),
};
let events_manager: ConsumerManager<EventConsumer> = ConsumerManager::new(
permit_pool.clone(),
event_consumer_stream,
event_worker_creator.clone(),
args.multitenant,
)
.await;
debug!("Creating command consumer manager");
let command_worker_creator = CommandWorkerCreator {
pool: connection_pool,
};
let commands_manager: ConsumerManager<CommandConsumer> = ConsumerManager::new(
permit_pool.clone(),
command_stream,
command_worker_creator.clone(),
args.multitenant,
)
.await;
// TODO(thomastaylor312): We might want to figure out how not to run this globally. Doing a
// synthetic event sent to the stream could be nice, but all the wadm processes would still fire
// off that tick, resulting in multiple people handling. We could maybe get it to work with the
// right duplicate window, but we have no idea when each process could fire a tick. Worst case
// scenario right now is that multiple fire simultaneously and a few of them just delete nothing
let reaper = Reaper::new(
state_storage.clone(),
Duration::from_secs(args.cleanup_interval / 2),
[],
);
let wadm_event_prefix = DEFAULT_WADM_EVENTS_TOPIC.trim_matches(trimmer);
debug!("Creating lattice observer");
let observer = observer::Observer {
parser: LatticeIdParser::new("wasmbus", args.multitenant),
command_manager: commands_manager,
event_manager: events_manager,
reaper,
client: client.clone(),
command_worker_creator,
event_worker_creator,
};
debug!("Subscribing to API topic");
let server = Server::new(
manifest_storage,
client,
Some(&args.api_prefix),
args.multitenant,
status_stream,
ManifestNotifier::new(wadm_event_prefix, context),
)
.await?;
let mut wadm = start_wadm(args).await.context("failed to run wadm")?;
tokio::select! {
res = server.serve() => {
res?
res = wadm.join_next() => {
match res {
Some(Ok(_)) => {
tracing::info!("WADM has exited successfully");
std::process::exit(0);
}
Some(Err(e)) => {
tracing::error!("WADM has exited with an error: {:?}", e);
std::process::exit(1);
}
None => {
tracing::info!("WADM server did not start");
std::process::exit(0);
}
}
}
res = observer.observe(wasmbus_event_subjects) => {
res?
_ = tokio::signal::ctrl_c() => {
tracing::info!("Received Ctrl+C, shutting down");
std::process::exit(0);
}
_ = tokio::signal::ctrl_c() => {}
}
Ok(())
}
#[derive(Clone)]
struct CommandWorkerCreator {
pool: ControlClientConstructor,
}
#[async_trait::async_trait]
impl WorkerCreator for CommandWorkerCreator {
type Output = CommandWorker;
async fn create(
&self,
lattice_id: &str,
multitenant_prefix: Option<&str>,
) -> anyhow::Result<Self::Output> {
let client = self.pool.get_connection(lattice_id, multitenant_prefix);
Ok(CommandWorker::new(client))
}
}
#[derive(Clone)]
struct EventWorkerCreator<StateStore> {
state_store: StateStore,
manifest_store: async_nats::jetstream::kv::Store,
pool: ControlClientConstructor,
command_topic_prefix: String,
publisher: Context,
notify_stream: Stream,
status_stream: Stream,
}
#[async_trait::async_trait]
impl<StateStore> WorkerCreator for EventWorkerCreator<StateStore>
where
StateStore: wadm::storage::Store + Send + Sync + Clone + 'static,
{
type Output = EventWorker<StateStore, wasmcloud_control_interface::Client, Context>;
async fn create(
&self,
lattice_id: &str,
multitenant_prefix: Option<&str>,
) -> anyhow::Result<Self::Output> {
let client = self.pool.get_connection(lattice_id, multitenant_prefix);
let command_publisher = CommandPublisher::new(
self.publisher.clone(),
&format!("{}.{lattice_id}", self.command_topic_prefix),
);
let status_publisher = StatusPublisher::new(
self.publisher.clone(),
Some(self.status_stream.clone()),
&format!("wadm.status.{lattice_id}"),
);
let manager = ScalerManager::new(
self.publisher.clone(),
self.notify_stream.clone(),
lattice_id,
multitenant_prefix,
self.state_store.clone(),
self.manifest_store.clone(),
command_publisher.clone(),
status_publisher.clone(),
client.clone(),
)
.await?;
Ok(EventWorker::new(
self.state_store.clone(),
client,
command_publisher,
status_publisher,
manager,
))
}
}

View File

@ -1,63 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: hello-all-hosts
annotations:
description: 'This is my app'
spec:
policies:
- name: test-policy
type: test
properties:
test: "data"
components:
- name: hello
type: component
properties:
image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
traits:
- type: spreadscaler
properties:
instances: 5
spread:
- name: eastcoast
requirements:
region: us-brooks-east
weight: 40
- name: westcoast
requirements:
region: us-taylor-west
weight: 40
- name: the-moon
requirements:
region: moon
weight: 20
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.21.0
traits:
- type: spreadscaler
properties:
instances: 5
spread:
- name: eastcoast
requirements:
region: us-brooks-east
weight: 40
- name: westcoast
requirements:
region: us-taylor-west
weight: 40
- name: the-moon
requirements:
region: moon
weight: 20
- type: link
properties:
target:
name: hello
namespace: wasi
package: http
interfaces: ['incoming-handler']

View File

@ -1,145 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: petclinic
annotations:
description: "wasmCloud Pet Clinic Sample"
spec:
components:
- name: ui
type: component
properties:
image: wasmcloud.azurecr.io/ui:0.3.2
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: uiclinicapp
requirements:
app: petclinic
- name: ui
type: component
properties:
image: wasmcloud.azurecr.io/customers:0.3.1
traits:
- type: linkdef
properties:
target:
name: postgres
config:
- name: petclinic-connect
properties:
uri: postgres://user:pass@your.db.host.com/petclinic
namespace: wasmcloud
package: sql
interfaces: ["query"]
- type: spreadscaler
properties:
instances: 1
spread:
- name: customersclinicapp
requirements:
app: petclinic
- name: vets
type: component
properties:
image: wasmcloud.azurecr.io/vets:0.3.1
traits:
- type: linkdef
properties:
target:
name: postgres
config:
- name: foobar
- name: petclinic-connect
properties:
uri: postgres://user:pass@your.db.host.com/petclinic
namespace: wasmcloud
package: sql
interfaces: ["query"]
- type: spreadscaler
properties:
instances: 1
spread:
- name: vetsclinicapp
requirements:
app: petclinic
- name: vets
type: component
properties:
image: wasmcloud.azurecr.io/visits:0.3.1
traits:
- type: linkdef
properties:
target:
name: postgres
config:
- name: petclinic-connect
properties:
uri: postgres://user:pass@your.db.host.com/petclinic
namespace: wasmcloud
package: sql
interfaces: ["query"]
- type: spreadscaler
properties:
instances: 1
spread:
- name: visitsclinicapp
requirements:
app: petclinic
- name: clinicapi
type: component
properties:
image: wasmcloud.azurecr.io/clinicapi:0.3.1
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: clinicapp
requirements:
app: petclinic
- name: httpserver
type: capability
properties:
image: wasmcloud.azurecr.io/httpserver:0.16.2
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: httpserverspread
requirements:
app: petclinic
- type: linkdef
properties:
target:
name: clinicapi
namespace: wasi
package: http
interfaces: ["incoming-handler"]
source:
config:
- name: default-port
properties:
address: "0.0.0.0:8080"
- name: postgres
type: capability
properties:
image: wasmcloud.azurecr.io/sqldb-postgres:0.3.1
traits:
- type: spreadscaler
properties:
instances: 1
spread:
- name: postgresspread
requirements:
app: petclinic

Some files were not shown because too many files have changed in this diff Show More