Skip to content
Open
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
2 changes: 1 addition & 1 deletion client/building_block_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ type meshBuildingBlockDefinitionClient struct {

func newBuildingBlockDefinitionClient(ctx context.Context, httpClient internal.HttpClient) meshBuildingBlockDefinitionClient {
return meshBuildingBlockDefinitionClient{
meshObject: internal.NewMeshObjectClient[MeshBuildingBlockDefinition](ctx, httpClient, "v1-preview"),
meshObject: internal.NewMeshObjectClient[MeshBuildingBlockDefinition](ctx, httpClient, "v1"),
}
}

Expand Down
2 changes: 1 addition & 1 deletion client/building_block_definition_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ type meshBuildingBlockDefinitionVersionClient struct {

func newBuildingBlockDefinitionVersionClient(ctx context.Context, httpClient internal.HttpClient) meshBuildingBlockDefinitionVersionClient {
return meshBuildingBlockDefinitionVersionClient{
meshObject: internal.NewMeshObjectClient[MeshBuildingBlockDefinitionVersion](ctx, httpClient, "v1-preview"),
meshObject: internal.NewMeshObjectClient[MeshBuildingBlockDefinitionVersion](ctx, httpClient, "v1"),
}
}

Expand Down
4 changes: 2 additions & 2 deletions client/building_block_definition_version_implementation.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package client

import (
"encoding/json/v2"
"fmt"

"github.com/meshcloud/meshstack-cli/client/types"
"github.com/meshcloud/meshstack-cli/client/types/enum"
"github.com/meshcloud/meshstack-cli/client/types/variant"
"github.com/meshcloud/meshstack-cli/internal/json"
)

type MeshBuildingBlockImplementationType string
Expand Down Expand Up @@ -100,7 +100,7 @@ func (m MeshBuildingBlockDefinitionImplementation) MarshalJSON() ([]byte, error)
}
w.Type = inferred
}
return json.Marshal(w, wireCompatibility)
return json.Marshal(w)
}

func (m *MeshBuildingBlockDefinitionImplementation) UnmarshalJSON(bytes []byte) error {
Expand Down
124 changes: 1 addition & 123 deletions client/building_block_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package client

import (
"context"
"encoding/json/jsontext"
"encoding/json/v2"
"errors"
"fmt"
Expand Down Expand Up @@ -57,127 +56,6 @@ type MeshBuildingBlockV2Spec struct {
// Inputs as pointer MeshBuildingBlockInput to support mocking secret responses.
Inputs map[string]*MeshBuildingBlockInput `json:"inputs" tfsdk:"inputs"`
ParentBuildingBlockRefs types.Set[UuidRef] `json:"parentBuildingBlockRefs" tfsdk:"parent_building_block_refs"`

// ParentBuildingBlocks is the deprecated parentBuildingBlocks field, which MarshalJSON and
// UnmarshalJSON carry. The deprecated meshstack_building_block_v2 surfaces of the Terraform
// provider read the definition uuid they report from it.
ParentBuildingBlocks types.Set[MeshBuildingBlockV2Parent] `json:"-" tfsdk:"-"`
}

// MeshBuildingBlockV2Parent is an entry of the deprecated parentBuildingBlocks field.
type MeshBuildingBlockV2Parent struct {
UuidRef

// BuildingBlockUuid always holds the same value as Uuid.
BuildingBlockUuid string `json:"buildingBlockUuid"`
// DefinitionUuid is the parent's building block definition. The backend derives it from the
// referenced block, so every response carries it and a request never does.
DefinitionUuid string `json:"definitionUuid,omitempty"`
}

// UnmarshalJSON fills Uuid from the deprecated buildingBlockUuid, which is where a response carries
// the parent's identity.
func (p *MeshBuildingBlockV2Parent) UnmarshalJSON(data []byte) error {
type wire MeshBuildingBlockV2Parent
var target wire
if err := json.Unmarshal(data, &target); err != nil {
return err
}

*p = MeshBuildingBlockV2Parent(target)
if p.Uuid == "" {
p.Uuid = p.BuildingBlockUuid
}
p.BuildingBlockUuid = p.Uuid
if p.Kind == "" {
p.Kind = MeshObjectKind.BuildingBlock
}

return nil
}

// wireCompatibility repeats the options internal/json marshals every request with: a v1-style
// MarshalJSON receives none of its caller's, so without it a nested value would go out in a
// different shape than the request around it.
var wireCompatibility = json.JoinOptions(
json.Deterministic(true),
json.FormatNilSliceAsNull(true),
json.FormatNilMapAsNull(true),
)

// MarshalJSON sends the parents under both field names: parentBuildingBlockRefs, and the deprecated
// parentBuildingBlocks for a backend that does not know the new field yet. A newer backend accepts
// both as long as they name the same building blocks, and an older one ignores the field it does not
// know, because the meshObject API does not reject unknown properties.
//
// Together with UnmarshalJSON this is the whole compatibility window. Once every backend still in use
// knows parentBuildingBlockRefs, both methods can go.
func (s MeshBuildingBlockV2Spec) MarshalJSON() ([]byte, error) {
type wire MeshBuildingBlockV2Spec
w := wire(s)
if len(w.ParentBuildingBlockRefs) == 0 {
w.ParentBuildingBlockRefs = parentRefsFromDeprecated(w.ParentBuildingBlocks)
}

var fields map[string]jsontext.Value
if encoded, err := json.Marshal(w, wireCompatibility); err != nil {
return nil, err
} else if err := json.Unmarshal(encoded, &fields); err != nil {
return nil, err
}

// The deprecated entry sends only buildingBlockUuid: every backend in the supported range reads
// the parent from it, and the definition uuid is always derived from the referenced block.
parents := make([]struct {
BuildingBlockUuid string `json:"buildingBlockUuid"`
}, 0, len(s.ParentBuildingBlockRefs))
for _, ref := range s.ParentBuildingBlockRefs {
parents = append(parents, struct {
BuildingBlockUuid string `json:"buildingBlockUuid"`
}{BuildingBlockUuid: ref.Uuid})
}
var err error
if fields["parentBuildingBlocks"], err = json.Marshal(parents); err != nil {
return nil, err
}

return json.Marshal(fields, wireCompatibility)
}

func parentRefsFromDeprecated(parents types.Set[MeshBuildingBlockV2Parent]) types.Set[UuidRef] {
refs := make(types.Set[UuidRef], 0, len(parents))
for _, parent := range parents {
refs = append(refs, UuidRef{Uuid: parent.Uuid, Kind: MeshObjectKind.BuildingBlock})
}
return refs
}

// UnmarshalJSON reads the parents from parentBuildingBlockRefs, or from the deprecated
// parentBuildingBlocks when a backend does not serve the new field yet. Terraform then sees the same
// elements against either backend and set hashing stays stable.
func (s *MeshBuildingBlockV2Spec) UnmarshalJSON(data []byte) error {
type wire MeshBuildingBlockV2Spec
var target struct {
wire

ParentBuildingBlocks types.Set[MeshBuildingBlockV2Parent] `json:"parentBuildingBlocks"`
}
if err := json.Unmarshal(data, &target); err != nil {
return err
}

*s = MeshBuildingBlockV2Spec(target.wire)
s.ParentBuildingBlocks = target.ParentBuildingBlocks
if len(s.ParentBuildingBlockRefs) == 0 {
s.ParentBuildingBlockRefs = parentRefsFromDeprecated(s.ParentBuildingBlocks)
}
for i := range s.ParentBuildingBlockRefs {
if s.ParentBuildingBlockRefs[i].Kind == "" {
s.ParentBuildingBlockRefs[i].Kind = MeshObjectKind.BuildingBlock
}
}

return nil
}

type MeshBuildingBlockInput struct {
Expand Down Expand Up @@ -305,7 +183,7 @@ type meshBuildingBlockV2Client struct {
}

func newBuildingBlockV2Client(ctx context.Context, httpClient internal.HttpClient) meshBuildingBlockV2Client {
return meshBuildingBlockV2Client{internal.NewMeshObjectClient[MeshBuildingBlockV2](ctx, httpClient, "v2-preview")}
return meshBuildingBlockV2Client{internal.NewMeshObjectClient[MeshBuildingBlockV2](ctx, httpClient, "v2")}
}

func (c meshBuildingBlockV2Client) Read(ctx context.Context, uuid string) (*MeshBuildingBlockV2, error) {
Expand Down
95 changes: 0 additions & 95 deletions client/buildingblock.go

This file was deleted.

4 changes: 1 addition & 3 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/meshcloud/meshstack-cli/internal/version"
)

var MinMeshStackVersion = version.MustParse("2026.36.0")
var MinMeshStackVersion = version.MustParse("2026.42.0")

// Version is re-exported because MinMeshStackVersion is one and internal/version is closed to
// another module.
Expand All @@ -21,7 +21,6 @@ type Authorization = http.Authorization

type Client struct {
ApiKey MeshApiKeyClient
BuildingBlock MeshBuildingBlockClient
BuildingBlockV2 MeshBuildingBlockV2Client
BuildingBlockRun MeshBuildingBlockRunClient
BuildingBlockDefinition MeshBuildingBlockDefinitionClient
Expand Down Expand Up @@ -61,7 +60,6 @@ func New(ctx context.Context, endpoint xurl.URL, client http.Client, auth Author
workspace := newWorkspaceClient(ctx, authorizedClient)
return Client{
ApiKey: newApiKeyClient(ctx, authorizedClient),
BuildingBlock: newBuildingBlockClient(ctx, authorizedClient),
BuildingBlockV2: buildingBlockV2,
BuildingBlockRun: buildingBlockRun,
BuildingBlockDefinition: buildingBlockDefinition,
Expand Down
1 change: 0 additions & 1 deletion client/client_kind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

func TestKind(t *testing.T) {
assert.Equal(t, internal.InferKind[MeshApiKey](), MeshObjectKind.ApiKey)
assert.Equal(t, internal.InferKind[MeshBuildingBlock](), MeshObjectKind.BuildingBlock)
assert.Equal(t, internal.InferKind[MeshBuildingBlockV2](), MeshObjectKind.BuildingBlock)
assert.Equal(t, internal.InferKind[MeshBuildingBlockDefinition](), MeshObjectKind.BuildingBlockDefinition)
assert.Equal(t, internal.InferKind[MeshBuildingBlockDefinitionVersion](), MeshObjectKind.BuildingBlockDefinitionVersion)
Expand Down
4 changes: 2 additions & 2 deletions client/integration_config.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package client

import (
"encoding/json/v2"
"fmt"

"github.com/meshcloud/meshstack-cli/client/types"
"github.com/meshcloud/meshstack-cli/client/types/enum"
"github.com/meshcloud/meshstack-cli/client/types/variant"
"github.com/meshcloud/meshstack-cli/internal/json"
)

type MeshIntegrationConfigType string
Expand Down Expand Up @@ -82,7 +82,7 @@ func (m MeshIntegrationConfig) MarshalJSON() ([]byte, error) {
}
w.Type = inferred
}
return json.Marshal(w, wireCompatibility)
return json.Marshal(w)
}

func (m *MeshIntegrationConfig) UnmarshalJSON(bytes []byte) error {
Expand Down
15 changes: 4 additions & 11 deletions client/types/variant/variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"reflect"

wirejson "github.com/meshcloud/meshstack-cli/internal/json"
)

// A Variant decodes one JSON value into X or into Y. When both decode, X wins, so Y can be a type
Expand All @@ -19,20 +21,11 @@ var (
_ json.Marshaler = Variant[int, string]{}
)

// wireCompatibility repeats the options internal/json marshals every request with: a v1-style
// MarshalJSON receives none of its caller's. A Y decoded from JSON is a map, so without
// Deterministic it would leave this method in a random member order.
var wireCompatibility = json.JoinOptions(
json.Deterministic(true),
json.FormatNilSliceAsNull(true),
json.FormatNilMapAsNull(true),
)

func (v Variant[X, Y]) MarshalJSON() ([]byte, error) {
if v.HasX() {
return json.Marshal(v.X, wireCompatibility)
return wirejson.Marshal(v.X)
} else if v.HasY() {
return json.Marshal(v.Y, wireCompatibility)
return wirejson.Marshal(v.Y)
}
return json.Marshal(nil)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/internal/testacc/testacc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ func (c *cli) environ() []string {
envEndpoint + "=" + c.endpoint,
envProfile + "=",
envWorkspace + "=",
setting.SkipVersionCheck.EnvKey() + "=",
// The backend under test reports its newest tag, which is below a floor raised for the coming release.
setting.SkipVersionCheck.EnvKey() + "=true",
setting.ApiKeyClientId.EnvKey() + "=",
setting.ApiKeyClientSecret.EnvKey() + "=",
setting.ApiToken.EnvKey() + "=",
Expand Down
8 changes: 3 additions & 5 deletions internal/json/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ import (
"path/filepath"
)

// wireCompatibility holds the output to the shape encoding/json v1 produced: a nil slice and a
// nil map go out as null rather than as [] and {}, and object members are sorted. json/v2 would
// write [] and {} and leave map order to chance, and neither change is ours to make — the
// meshObject API may read null and an empty collection differently on a PUT, and the Terraform
// provider hashes this JSON to decide whether a resource changed.
// wireCompatibility keeps the output in the shape encoding/json v1 wrote, because the meshObject API
// reads a null collection differently from an empty one. Sorted map keys keep the files written here
// stable. A MarshalJSON method gets none of its caller's options, so it marshals through [Marshal].
var wireCompatibility = json.JoinOptions(
json.Deterministic(true),
json.FormatNilSliceAsNull(true),
Expand Down
Loading
Loading