Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .badges/operations.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions .badges/parity.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions .badges/services.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion AZURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ That said, MQTT's fixed port (`1883`) happens to fall *outside* `--port-range-st
- **M0 (done)** — `pkgs/azureauth` (SharedKey canonicalization + fixed devstoreaccount1 constants); `services/azureblob` wired into `cli.go` and bound to its own fixed port (synchronous bind, fail-fast, no `PortAlloc` fallback — see section 4's port-selection note); Create/Delete/List Container, Put/Get/Delete Blob, List Blobs; `PARITY.md`; unit tests + Go integration tests using `azure-sdk-for-go`. See `services/azureblob/README.md`/`PARITY.md` for current status and known gaps.
- **M1** — Blob completeness: properties/metadata, block-blob multipart (Put Block/Put Block List), conditional headers (`If-Match`/`If-None-Match`), error-mapping table (mirrors `services/sqs`'s `errorDetails` pattern).
- **M2 (done)** — `services/azurequeue`: List/Create/Delete Queue, full message lifecycle (Put/Get/Peek/Delete/Update/Clear Messages), visibility timeout with pop-receipt rotation, message TTL swept by a background `Janitor`, wired into `cli.go` and bound to its own fixed port (10001, Azurite's own Queue port; synchronous bind, fail-fast, no `PortAlloc` fallback -- mirrors Azure Blob's M0 port strategy, see section 4). See `services/azurequeue/README.md`/`PARITY.md` for current status and known gaps.
- **M3** — `services/azuretable`: table CRUD, entity insert/get/query/update/merge/delete, `$filter` subset (eq/ne/lt/gt/and/or on partition/row key plus scalar properties), ETag-based optimistic concurrency.
- **M3 (done)** — `services/azuretable`: table CRUD (Create/Delete/List Table), full entity lifecycle (Insert/Get/Query/Replace/Merge/Delete), a hand-written `$filter` lexer/parser/evaluator (eq/ne/lt/le/gt/ge, and/or/not, parentheses, every OData literal form -- string/int/Int64/float/bool/datetime/guid/binary -- against `PartitionKey`/`RowKey`/`Timestamp` and scalar properties), all eight EDM property types (String/Int32/Int64/Double/Boolean/DateTime/Guid/Binary) with `@odata.type` annotation round-tripping matching `azure-sdk-for-go/sdk/data/aztables`'s own client-side inference, and ETag-based optimistic concurrency (If-Match `*`/specific/absent -> upsert), wired into `cli.go` and bound to its own fixed port (10002, Azurite's own Table port; synchronous bind, fail-fast, no `PortAlloc` fallback -- mirrors Blob's M0 and Queue's M2 port strategy, see section 4). Batch (`$batch` multipart/mixed changesets) and continuation-token pagination are deferred -- see `services/azuretable/PARITY.md`. (Note the milestone-numbering skew already flagged in `services/azurequeue/PARITY.md`'s `deferred:` entry: this repo's section-8 list calls Table "M3", while the implementation task that built it used the internal name "M2".) See `services/azuretable/README.md`/`PARITY.md` for current status and known gaps.
- **M4** — `services/cosmosdb`: database/container CRUD (with partition-key-path declaration), document CRUD, SQL-subset query engine, fake RU/session-token/etag headers; scope the op list against cosmium (github.com/pikami/cosmium) as reference prior art; integration tests against `azure-sdk-for-go`, `azure-sdk-for-js`, and `azure-cosmos` (Python).
- **M5** — Docs/polish: root README services table + badges/icons, `docs/services/*.md` guides, a docker-compose example under `examples/`, and the `test/e2e` cross-SDK smoke suite covering all four services.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ Every service links to its own page with a coverage breakdown — audited operat
| [AppStream 2.0](services/appstream/README.md) | A | 44 | clean |
| [Azureblob](services/azureblob/README.md) | C | 8 | 8 gaps; 2 deferred |
| [Azurequeue](services/azurequeue/README.md) | C | 9 | 7 gaps; 1 deferred |
| [Azuretable](services/azuretable/README.md) | C | 10 | 6 gaps; 3 deferred |
| [Cloudfrontkeyvaluestore](services/cloudfrontkeyvaluestore/README.md) | B | 6 | 3 gaps; 1 structural gap |
| [Directconnect](services/directconnect/README.md) | A | 64 | 3 gaps; 8 structural gaps; 1 deferred |
| [Grafana](services/grafana/README.md) | A | 25 | 2 gaps; 1 structural gap |
Expand Down
19 changes: 19 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import (
awsconfigbackend "github.com/blackbirdworks/gopherstack/services/awsconfig"
azureblobbackend "github.com/blackbirdworks/gopherstack/services/azureblob"
azurequeuebackend "github.com/blackbirdworks/gopherstack/services/azurequeue"
azuretablebackend "github.com/blackbirdworks/gopherstack/services/azuretable"
backupbackend "github.com/blackbirdworks/gopherstack/services/backup"
batchbackend "github.com/blackbirdworks/gopherstack/services/batch"
bedrockbackend "github.com/blackbirdworks/gopherstack/services/bedrock"
Expand Down Expand Up @@ -462,6 +463,7 @@ type CLI struct {
StepFunctions sfnbackend.Settings `embed:"" prefix:"stepfunctions-"`
AzureBlob azureblobbackend.Settings `embed:"" prefix:"azure-blob-"`
AzureQueue azurequeuebackend.Settings `embed:"" prefix:"azure-queue-"`
AzureTable azuretablebackend.Settings `embed:"" prefix:"azure-table-"`
PortRangeStart int ` name:"port-range-start" env:"PORT_RANGE_START" default:"10000" help:"Start of the port range for resource endpoints."` //nolint:lll // config struct tags are intentionally verbose
PortRangeEnd int ` name:"port-range-end" env:"PORT_RANGE_END" default:"10100" help:"End (exclusive) of the port range for resource endpoints."` //nolint:lll // config struct tags are intentionally verbose
EC2DockerSSHPortMin int ` name:"ec2-docker-ssh-port-min" env:"EC2_DOCKER_SSH_PORT_MIN" default:"0" help:"Lower bound of the host TCP port range used to map EC2-docker SSH (0 = let Docker pick)."` //nolint:lll // config struct tags are intentionally verbose
Expand Down Expand Up @@ -535,6 +537,11 @@ func (c *CLI) GetAzureQueueSettings() azurequeuebackend.Settings {
return c.AzureQueue
}

// GetAzureTableSettings returns Azure Table settings (azuretable.ConfigProvider).
func (c *CLI) GetAzureTableSettings() azuretablebackend.Settings {
return c.AzureTable
}

// GetS3Endpoint returns the configured S3 endpoint (s3.ConfigProvider).
func (c *CLI) GetS3Endpoint() string {
s3Port := strings.TrimPrefix(c.Port, ":")
Expand Down Expand Up @@ -1897,6 +1904,17 @@ func reserveFixedServicePorts(ctx context.Context, log *slog.Logger, alloc *port
log.WarnContext(ctx, "failed to reserve AzureQueue's fixed port in the shared pool",
"port", cli.AzureQueue.Port, "error", err)
}

// AzureTable's dedicated listener (services/azuretable) binds its own
// fixed, protocol-conventional default port (10002, matching Azurite's
// own Table service port) the same way AzureBlob/AzureQueue do above --
// see those calls' comments and AZURE.md section 4 for the full
// rationale. It sits in the same PortRangeStart/PortRangeEnd default
// range, so it needs the same reservation.
if err := alloc.Reserve(cli.AzureTable.Port, "azuretable"); err != nil {
log.WarnContext(ctx, "failed to reserve AzureTable's fixed port in the shared pool",
"port", cli.AzureTable.Port, "error", err)
}
}

// setupPortAllocatorWithReservations builds the shared port allocator and
Expand Down Expand Up @@ -3632,6 +3650,7 @@ func getMostRecentServiceProviders() []service.Provider {
return []service.Provider{
&azureblobbackend.Provider{},
&azurequeuebackend.Provider{},
&azuretablebackend.Provider{},
&pinpointbackend.Provider{},
&pipesbackend.Provider{},
&accessanalyzerbackend.Provider{},
Expand Down
59 changes: 59 additions & 0 deletions cli_azuretable_port_reservation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"log/slog"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/blackbirdworks/gopherstack/pkgs/portalloc"
azuretablebackend "github.com/blackbirdworks/gopherstack/services/azuretable"
)

// TestReserveFixedServicePorts_AzureTable is a sibling to
// cli_azureblob_port_reservation_test.go's TestReserveFixedServicePorts,
// covering AzureTable's own fixed-port reservation instead of restructuring
// that file's AzureBlob-only test table: services/azuretable binds its
// dedicated listener directly via net.Listen, not through PortAlloc, but its
// default port (10002) sits inside PortRangeStart/PortRangeEnd's own default
// range (10000-10100). Without reserving it, PortAlloc could still hand that
// same port number to an unrelated caller (e.g. ElastiCache), which would
// only surface later as a confusing address-in-use failure. See AZURE.md
// section 4 and pkgs/portalloc.Allocator.Reserve's doc comment.
func TestReserveFixedServicePorts_AzureTable(t *testing.T) {
t.Parallel()

tests := []struct {
name string
azurePort int
rangeStart int
rangeEnd int
wantBlockedFromPool bool
}{
{
name: "default azure table port collides with default pool range",
azurePort: azuretablebackend.DefaultPort, rangeStart: 10000, rangeEnd: 10100,
wantBlockedFromPool: true,
},
{
name: "custom azure table port outside a custom pool range",
azurePort: 9998, rangeStart: 10000, rangeEnd: 10100,
wantBlockedFromPool: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

alloc, err := portalloc.New(tt.rangeStart, tt.rangeEnd)
require.NoError(t, err)

cli := CLI{AzureTable: azuretablebackend.Settings{Port: tt.azurePort}}
reserveFixedServicePorts(t.Context(), slog.Default(), alloc, cli)

assert.Equal(t, tt.wantBlockedFromPool, alloc.IsAllocated(tt.azurePort), tt.name)
})
}
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ require github.com/aws/aws-sdk-go-v2/service/omics v1.49.5
require github.com/aws/aws-sdk-go-v2/service/cleanrooms v1.49.4

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.1
github.com/aws/aws-sdk-go-v2/service/account v1.35.4
Expand All @@ -229,7 +231,6 @@ require (
)

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 h1:aokoqcHvaGjiM3VpjKDfMMnF
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0/go.mod h1:/WYEx9pcM9Y+Dd/APJaNlSvVSvzl54rrMdZT5+Oi2LM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.1 h1:j0hhYS006eJ54vusoap0f2NVZ1YY3QnaAEnLM68f0SQ=
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.1/go.mod h1:AdtInaXmK8eYmbjezRWgLz+Qs46nc9Up9GWGwteWNfw=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
Expand Down
16 changes: 16 additions & 0 deletions pkgs/persistence/testdata/snapshot_inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -2531,6 +2531,22 @@
],
"version": 1
},
"azuretable": {
"fields": [
"EntityProperty.Type EdmType",
"EntityProperty.Value any",
"backendSnapshot.Tables map[string]*storedTable `json:\"tables\"`",
"entityCompositeKey.PartitionKey string",
"entityCompositeKey.RowKey string",
"storedEntity.PartitionKey string",
"storedEntity.Properties map[string]EntityProperty",
"storedEntity.RowKey string",
"storedEntity.Timestamp time.Time",
"storedTable.Entities map[entityCompositeKey]*storedEntity",
"storedTable.Name string"
],
"version": 2
},
"backup": {
"fields": [
"AdvancedBackupSetting.BackupOptions map[string]string `json:\"backupOptions,omitempty\"`",
Expand Down
Loading
Loading