Skip to content

Commit 5ee72f1

Browse files
add azure emulator client so lstk status shows version
1 parent 5846520 commit 5ee72f1

3 files changed

Lines changed: 119 additions & 0 deletions

File tree

cmd/status.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/localstack/lstk/internal/container"
99
"github.com/localstack/lstk/internal/emulator"
1010
"github.com/localstack/lstk/internal/emulator/aws"
11+
"github.com/localstack/lstk/internal/emulator/azure"
1112
"github.com/localstack/lstk/internal/emulator/snowflake"
1213
"github.com/localstack/lstk/internal/env"
1314
"github.com/localstack/lstk/internal/output"
@@ -35,6 +36,7 @@ func newStatusCmd(cfg *env.Env) *cobra.Command {
3536
clients := map[config.EmulatorType]emulator.Client{
3637
config.EmulatorAWS: aws.NewClient(),
3738
config.EmulatorSnowflake: snowflake.NewClient(),
39+
config.EmulatorAzure: azure.NewClient(),
3840
}
3941

4042
if isInteractiveMode(cfg) {

internal/emulator/azure/client.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package azure
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
9+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
10+
11+
"github.com/localstack/lstk/internal/emulator"
12+
)
13+
14+
type Client struct {
15+
http *http.Client
16+
}
17+
18+
func NewClient() *Client {
19+
return &Client{
20+
http: &http.Client{
21+
Transport: otelhttp.NewTransport(
22+
http.DefaultTransport,
23+
otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
24+
return "azure " + r.Method + " " + r.URL.Path
25+
}),
26+
),
27+
},
28+
}
29+
}
30+
31+
type infoResponse struct {
32+
Version string `json:"version"`
33+
}
34+
35+
// FetchVersion reads the version from /_localstack/info. The Azure image's
36+
// /_localstack/health response does not carry a "version" field, so /info is
37+
// the only endpoint that surfaces it.
38+
func (c *Client) FetchVersion(ctx context.Context, host string) (string, error) {
39+
url := fmt.Sprintf("http://%s/_localstack/info", host)
40+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
41+
if err != nil {
42+
return "", fmt.Errorf("failed to create info request: %w", err)
43+
}
44+
45+
resp, err := c.http.Do(req)
46+
if err != nil {
47+
return "", fmt.Errorf("failed to fetch info: %w", err)
48+
}
49+
defer func() { _ = resp.Body.Close() }()
50+
51+
if resp.StatusCode != http.StatusOK {
52+
return "", fmt.Errorf("info endpoint returned status %d", resp.StatusCode)
53+
}
54+
55+
var i infoResponse
56+
if err := json.NewDecoder(resp.Body).Decode(&i); err != nil {
57+
return "", fmt.Errorf("failed to decode info response: %w", err)
58+
}
59+
return i.Version, nil
60+
}
61+
62+
// FetchResources is a no-op for Azure — the emulator does not expose
63+
// /_localstack/resources (returns 404).
64+
func (c *Client) FetchResources(_ context.Context, _ string) ([]emulator.Resource, error) {
65+
return nil, nil
66+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package azure
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestFetchVersion(t *testing.T) {
15+
t.Parallel()
16+
17+
t.Run("returns version from info endpoint", func(t *testing.T) {
18+
t.Parallel()
19+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20+
assert.Equal(t, "/_localstack/info", r.URL.Path)
21+
w.Header().Set("Content-Type", "application/json")
22+
_, _ = fmt.Fprintln(w, `{"version": "2026.6.0.dev112:17a29a966", "edition": "azure-alpha"}`)
23+
}))
24+
defer server.Close()
25+
26+
c := NewClient()
27+
version, err := c.FetchVersion(context.Background(), server.Listener.Addr().String())
28+
require.NoError(t, err)
29+
assert.Equal(t, "2026.6.0.dev112:17a29a966", version)
30+
})
31+
32+
t.Run("returns error on non-200", func(t *testing.T) {
33+
t.Parallel()
34+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
35+
w.WriteHeader(http.StatusInternalServerError)
36+
}))
37+
defer server.Close()
38+
39+
c := NewClient()
40+
_, err := c.FetchVersion(context.Background(), server.Listener.Addr().String())
41+
require.Error(t, err)
42+
})
43+
}
44+
45+
func TestFetchResources_AlwaysEmpty(t *testing.T) {
46+
t.Parallel()
47+
c := NewClient()
48+
rows, err := c.FetchResources(context.Background(), "unused")
49+
require.NoError(t, err)
50+
assert.Empty(t, rows)
51+
}

0 commit comments

Comments
 (0)