From 90dec3bced127f198155f8f2fef4e2e9611fbcdd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 05:51:55 +0000 Subject: [PATCH 1/5] Initial plan From ec07245c94769e0a9c896ca77fb62ef284cfd691 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 05:58:24 +0000 Subject: [PATCH 2/5] Allow binary protobuf payload in apply -f via content-based detection Co-authored-by: bcho <1975118+bcho@users.noreply.github.com> --- pkg/cmd/apply/apply.go | 89 ++++++++++++++++++++++------ pkg/cmd/apply/apply_test.go | 113 ++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 17 deletions(-) create mode 100644 pkg/cmd/apply/apply_test.go diff --git a/pkg/cmd/apply/apply.go b/pkg/cmd/apply/apply.go index c8502ca..1b714f9 100644 --- a/pkg/cmd/apply/apply.go +++ b/pkg/cmd/apply/apply.go @@ -44,7 +44,8 @@ var Command = &cobra.Command{ return err } - return apply(cmd.Context(), input, flagNoPrettyUI) + isBinaryProto := isBinaryProtoContent(input) + return apply(cmd.Context(), input, isBinaryProto, flagNoPrettyUI) }, SilenceUsage: true, } @@ -76,35 +77,45 @@ type stepResult struct { err error } -func apply(ctx context.Context, input []byte, noPrettyUI bool) error { +func apply(ctx context.Context, input []byte, isBinaryProto bool, noPrettyUI bool) error { conn, err := inmem.NewConnection() if err != nil { return err } defer conn.Close() //nolint:errcheck // close connection - tok, err := json.NewDecoder(bytes.NewBuffer(input)).Token() - if err != nil { - return err - } + var parsed []parsedAction - var bs []json.RawMessage - if tok == json.Delim('[') { - if err := json.Unmarshal(input, &bs); err != nil { + if isBinaryProto { + pa, err := parseActionFromProto(input) + if err != nil { return err } + parsed = []parsedAction{pa} } else { - bs = append(bs, input) - } - - // Pre-parse all actions so we know the total count and names up front. - parsed := make([]parsedAction, 0, len(bs)) - for _, b := range bs { - pa, err := parseAction(b) + tok, err := json.NewDecoder(bytes.NewBuffer(input)).Token() if err != nil { return err } - parsed = append(parsed, pa) + + var bs []json.RawMessage + if tok == json.Delim('[') { + if err := json.Unmarshal(input, &bs); err != nil { + return err + } + } else { + bs = append(bs, input) + } + + // Pre-parse all actions so we know the total count and names up front. + parsed = make([]parsedAction, 0, len(bs)) + for _, b := range bs { + pa, err := parseAction(b) + if err != nil { + return err + } + parsed = append(parsed, pa) + } } if noPrettyUI { @@ -244,6 +255,21 @@ func formatDuration(d time.Duration) string { } } +// isBinaryProtoContent reports whether input appears to be a binary-encoded +// protobuf message. It inspects the first non-whitespace byte: JSON objects +// begin with '{' and JSON arrays begin with '['; binary protobuf never starts +// with either of those bytes, and the apply command only accepts JSON objects +// or arrays. +func isBinaryProtoContent(input []byte) bool { + for _, b := range input { + if b == ' ' || b == '\t' || b == '\r' || b == '\n' { + continue + } + return b != '{' && b != '[' + } + return false +} + func parseAction(b []byte) (parsedAction, error) { base := &api.Base{} if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(b, base); err != nil { @@ -271,3 +297,32 @@ func parseAction(b []byte) (parsedAction, error) { return parsedAction{name: name, message: m}, nil } + +// parseActionFromProto deserializes a single binary protobuf-encoded action. +func parseActionFromProto(b []byte) (parsedAction, error) { + base := &api.Base{} + if err := proto.Unmarshal(b, base); err != nil { + return parsedAction{}, err + } + + actionType := base.GetMetadata().GetType() + actionName := base.GetMetadata().GetName() + + mt, err := protoregistry.GlobalTypes.FindMessageByURL(actionType) + if err != nil { + return parsedAction{}, fmt.Errorf("lookup action type %q: %w", actionType, err) + } + + m := mt.New().Interface() + if err := proto.Unmarshal(b, m); err != nil { + return parsedAction{}, fmt.Errorf("unmarshal action %q: %w", actionType, err) + } + + // Use the action name if available, otherwise fall back to the type URL. + name := actionName + if name == "" { + name = actionType + } + + return parsedAction{name: name, message: m}, nil +} diff --git a/pkg/cmd/apply/apply_test.go b/pkg/cmd/apply/apply_test.go new file mode 100644 index 0000000..1278b93 --- /dev/null +++ b/pkg/cmd/apply/apply_test.go @@ -0,0 +1,113 @@ +package apply + +import ( + "testing" + + "google.golang.org/protobuf/proto" + + "github.com/Azure/AKSFlexNode/components/api" + _ "github.com/Azure/AKSFlexNode/components/linux" // register linux action types +) + +func TestIsBinaryProtoContent(t *testing.T) { + tests := []struct { + name string + input []byte + want bool + }{ + {name: "json object", input: []byte(`{"metadata":{}}`), want: false}, + {name: "json array", input: []byte(`[{"metadata":{}}]`), want: false}, + {name: "json object with leading whitespace", input: []byte(" \t\n{\"metadata\":{}}"), want: false}, + {name: "json array with leading whitespace", input: []byte("\n [{\"metadata\":{}}]"), want: false}, + {name: "binary proto (non-json first byte)", input: []byte{0x0a, 0x00}, want: true}, + {name: "empty input", input: []byte{}, want: false}, + {name: "whitespace only", input: []byte(" "), want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isBinaryProtoContent(tt.input) + if got != tt.want { + t.Errorf("isBinaryProtoContent(%q) = %v, want %v", tt.input, got, tt.want) + } + }) + } +} + +func TestParseActionFromProto(t *testing.T) { + // Build a binary-encoded Base message that carries the ConfigureBaseOS type URL. + base := &api.Base{} + base.SetMetadata(&api.Metadata{}) + base.GetMetadata().SetType("aks.flex.components.linux.ConfigureBaseOS") + base.GetMetadata().SetName("test-action") + + b, err := proto.Marshal(base) + if err != nil { + t.Fatalf("proto.Marshal: %v", err) + } + + pa, err := parseActionFromProto(b) + if err != nil { + t.Fatalf("parseActionFromProto: %v", err) + } + + if pa.name != "test-action" { + t.Errorf("name = %q, want %q", pa.name, "test-action") + } + if pa.message == nil { + t.Error("message is nil") + } +} + +func TestParseActionFromProto_FallbackName(t *testing.T) { + // When no name is set the type URL is used as the display name. + base := &api.Base{} + base.SetMetadata(&api.Metadata{}) + base.GetMetadata().SetType("aks.flex.components.linux.ConfigureBaseOS") + + b, err := proto.Marshal(base) + if err != nil { + t.Fatalf("proto.Marshal: %v", err) + } + + pa, err := parseActionFromProto(b) + if err != nil { + t.Fatalf("parseActionFromProto: %v", err) + } + + want := "aks.flex.components.linux.ConfigureBaseOS" + if pa.name != want { + t.Errorf("name = %q, want %q", pa.name, want) + } +} + +func TestParseActionFromProto_UnknownType(t *testing.T) { + base := &api.Base{} + base.SetMetadata(&api.Metadata{}) + base.GetMetadata().SetType("does.not.Exist") + + b, err := proto.Marshal(base) + if err != nil { + t.Fatalf("proto.Marshal: %v", err) + } + + if _, err := parseActionFromProto(b); err == nil { + t.Error("expected error for unknown type, got nil") + } +} + +func TestIsBinaryProtoContent_RoundTrip(t *testing.T) { + // A real serialized proto message must be detected as binary. + base := &api.Base{} + base.SetMetadata(&api.Metadata{}) + base.GetMetadata().SetType("aks.flex.components.linux.ConfigureBaseOS") + + b, err := proto.Marshal(base) + if err != nil { + t.Fatalf("proto.Marshal: %v", err) + } + + if !isBinaryProtoContent(b) { + t.Error("expected isBinaryProtoContent to return true for a marshaled proto message") + } +} From b6b5cdfee0173bbca0ccede07ab1b2e95b46fbef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:10:41 +0000 Subject: [PATCH 3/5] apply: parse before apply, use isJSONContent for format detection Co-authored-by: bcho <1975118+bcho@users.noreply.github.com> --- pkg/cmd/apply/apply.go | 91 +++++++++++++++++++------------------ pkg/cmd/apply/apply_test.go | 32 ++++++++----- 2 files changed, 67 insertions(+), 56 deletions(-) diff --git a/pkg/cmd/apply/apply.go b/pkg/cmd/apply/apply.go index 1b714f9..157d11d 100644 --- a/pkg/cmd/apply/apply.go +++ b/pkg/cmd/apply/apply.go @@ -44,8 +44,11 @@ var Command = &cobra.Command{ return err } - isBinaryProto := isBinaryProtoContent(input) - return apply(cmd.Context(), input, isBinaryProto, flagNoPrettyUI) + parsed, err := parseActions(input) + if err != nil { + return err + } + return apply(cmd.Context(), parsed, flagNoPrettyUI) }, SilenceUsage: true, } @@ -77,47 +80,13 @@ type stepResult struct { err error } -func apply(ctx context.Context, input []byte, isBinaryProto bool, noPrettyUI bool) error { +func apply(ctx context.Context, parsed []parsedAction, noPrettyUI bool) error { conn, err := inmem.NewConnection() if err != nil { return err } defer conn.Close() //nolint:errcheck // close connection - var parsed []parsedAction - - if isBinaryProto { - pa, err := parseActionFromProto(input) - if err != nil { - return err - } - parsed = []parsedAction{pa} - } else { - tok, err := json.NewDecoder(bytes.NewBuffer(input)).Token() - if err != nil { - return err - } - - var bs []json.RawMessage - if tok == json.Delim('[') { - if err := json.Unmarshal(input, &bs); err != nil { - return err - } - } else { - bs = append(bs, input) - } - - // Pre-parse all actions so we know the total count and names up front. - parsed = make([]parsedAction, 0, len(bs)) - for _, b := range bs { - pa, err := parseAction(b) - if err != nil { - return err - } - parsed = append(parsed, pa) - } - } - if noPrettyUI { return applyPlain(ctx, conn, parsed) } @@ -255,21 +224,55 @@ func formatDuration(d time.Duration) string { } } -// isBinaryProtoContent reports whether input appears to be a binary-encoded -// protobuf message. It inspects the first non-whitespace byte: JSON objects -// begin with '{' and JSON arrays begin with '['; binary protobuf never starts -// with either of those bytes, and the apply command only accepts JSON objects -// or arrays. -func isBinaryProtoContent(input []byte) bool { +// isJSONContent reports whether input appears to be a JSON object or array. +// It inspects the first non-whitespace byte: JSON objects begin with '{' and +// JSON arrays begin with '['; binary protobuf never starts with either byte. +func isJSONContent(input []byte) bool { for _, b := range input { if b == ' ' || b == '\t' || b == '\r' || b == '\n' { continue } - return b != '{' && b != '[' + return b == '{' || b == '[' } return false } +// parseActions detects the input format and returns the pre-parsed actions. +func parseActions(input []byte) ([]parsedAction, error) { + if isJSONContent(input) { + tok, err := json.NewDecoder(bytes.NewBuffer(input)).Token() + if err != nil { + return nil, err + } + + var bs []json.RawMessage + if tok == json.Delim('[') { + if err := json.Unmarshal(input, &bs); err != nil { + return nil, err + } + } else { + bs = append(bs, input) + } + + // Pre-parse all actions so we know the total count and names up front. + parsed := make([]parsedAction, 0, len(bs)) + for _, b := range bs { + pa, err := parseAction(b) + if err != nil { + return nil, err + } + parsed = append(parsed, pa) + } + return parsed, nil + } + + pa, err := parseActionFromProto(input) + if err != nil { + return nil, err + } + return []parsedAction{pa}, nil +} + func parseAction(b []byte) (parsedAction, error) { base := &api.Base{} if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(b, base); err != nil { diff --git a/pkg/cmd/apply/apply_test.go b/pkg/cmd/apply/apply_test.go index 1278b93..9dd4b0d 100644 --- a/pkg/cmd/apply/apply_test.go +++ b/pkg/cmd/apply/apply_test.go @@ -9,26 +9,26 @@ import ( _ "github.com/Azure/AKSFlexNode/components/linux" // register linux action types ) -func TestIsBinaryProtoContent(t *testing.T) { +func TestIsJSONContent(t *testing.T) { tests := []struct { name string input []byte want bool }{ - {name: "json object", input: []byte(`{"metadata":{}}`), want: false}, - {name: "json array", input: []byte(`[{"metadata":{}}]`), want: false}, - {name: "json object with leading whitespace", input: []byte(" \t\n{\"metadata\":{}}"), want: false}, - {name: "json array with leading whitespace", input: []byte("\n [{\"metadata\":{}}]"), want: false}, - {name: "binary proto (non-json first byte)", input: []byte{0x0a, 0x00}, want: true}, + {name: "json object", input: []byte(`{"metadata":{}}`), want: true}, + {name: "json array", input: []byte(`[{"metadata":{}}]`), want: true}, + {name: "json object with leading whitespace", input: []byte(" \t\n{\"metadata\":{}}"), want: true}, + {name: "json array with leading whitespace", input: []byte("\n [{\"metadata\":{}}]"), want: true}, + {name: "binary proto (non-json first byte)", input: []byte{0x0a, 0x00}, want: false}, {name: "empty input", input: []byte{}, want: false}, {name: "whitespace only", input: []byte(" "), want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := isBinaryProtoContent(tt.input) + got := isJSONContent(tt.input) if got != tt.want { - t.Errorf("isBinaryProtoContent(%q) = %v, want %v", tt.input, got, tt.want) + t.Errorf("isJSONContent(%q) = %v, want %v", tt.input, got, tt.want) } }) } @@ -96,18 +96,26 @@ func TestParseActionFromProto_UnknownType(t *testing.T) { } } -func TestIsBinaryProtoContent_RoundTrip(t *testing.T) { - // A real serialized proto message must be detected as binary. +func TestParseActions_BinaryProtoRoundTrip(t *testing.T) { + // A real serialized proto message must be routed to the binary proto parser. base := &api.Base{} base.SetMetadata(&api.Metadata{}) base.GetMetadata().SetType("aks.flex.components.linux.ConfigureBaseOS") + base.GetMetadata().SetName("round-trip") b, err := proto.Marshal(base) if err != nil { t.Fatalf("proto.Marshal: %v", err) } - if !isBinaryProtoContent(b) { - t.Error("expected isBinaryProtoContent to return true for a marshaled proto message") + parsed, err := parseActions(b) + if err != nil { + t.Fatalf("parseActions: %v", err) + } + if len(parsed) != 1 { + t.Fatalf("len(parsed) = %d, want 1", len(parsed)) + } + if parsed[0].name != "round-trip" { + t.Errorf("name = %q, want %q", parsed[0].name, "round-trip") } } From 725bb943da80726d39c64eb5a29958120d522586 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:15:36 +0000 Subject: [PATCH 4/5] apply: extract parseActionFromJSON, move parsing to parse.go Co-authored-by: bcho <1975118+bcho@users.noreply.github.com> --- pkg/cmd/apply/apply.go | 111 ------------------------------------ pkg/cmd/apply/parse.go | 125 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 111 deletions(-) create mode 100644 pkg/cmd/apply/parse.go diff --git a/pkg/cmd/apply/apply.go b/pkg/cmd/apply/apply.go index 157d11d..066a63c 100644 --- a/pkg/cmd/apply/apply.go +++ b/pkg/cmd/apply/apply.go @@ -1,9 +1,7 @@ package apply import ( - "bytes" "context" - "encoding/json" "fmt" "io" "os" @@ -13,11 +11,8 @@ import ( "github.com/schollz/progressbar/v3" "github.com/spf13/cobra" "google.golang.org/grpc" - "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoregistry" - "github.com/Azure/AKSFlexNode/components/api" "github.com/Azure/AKSFlexNode/components/services/actions" "github.com/Azure/AKSFlexNode/components/services/inmem" ) @@ -223,109 +218,3 @@ func formatDuration(d time.Duration) string { return fmt.Sprintf("%.1fs", d.Seconds()) } } - -// isJSONContent reports whether input appears to be a JSON object or array. -// It inspects the first non-whitespace byte: JSON objects begin with '{' and -// JSON arrays begin with '['; binary protobuf never starts with either byte. -func isJSONContent(input []byte) bool { - for _, b := range input { - if b == ' ' || b == '\t' || b == '\r' || b == '\n' { - continue - } - return b == '{' || b == '[' - } - return false -} - -// parseActions detects the input format and returns the pre-parsed actions. -func parseActions(input []byte) ([]parsedAction, error) { - if isJSONContent(input) { - tok, err := json.NewDecoder(bytes.NewBuffer(input)).Token() - if err != nil { - return nil, err - } - - var bs []json.RawMessage - if tok == json.Delim('[') { - if err := json.Unmarshal(input, &bs); err != nil { - return nil, err - } - } else { - bs = append(bs, input) - } - - // Pre-parse all actions so we know the total count and names up front. - parsed := make([]parsedAction, 0, len(bs)) - for _, b := range bs { - pa, err := parseAction(b) - if err != nil { - return nil, err - } - parsed = append(parsed, pa) - } - return parsed, nil - } - - pa, err := parseActionFromProto(input) - if err != nil { - return nil, err - } - return []parsedAction{pa}, nil -} - -func parseAction(b []byte) (parsedAction, error) { - base := &api.Base{} - if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(b, base); err != nil { - return parsedAction{}, err - } - - actionType := base.GetMetadata().GetType() - actionName := base.GetMetadata().GetName() - - mt, err := protoregistry.GlobalTypes.FindMessageByURL(actionType) - if err != nil { - return parsedAction{}, fmt.Errorf("lookup action type %q: %w", actionType, err) - } - - m := mt.New().Interface() - if err := protojson.Unmarshal(b, m); err != nil { - return parsedAction{}, fmt.Errorf("unmarshal action %q: %w", actionType, err) - } - - // Use the action name if available, otherwise fall back to the type URL. - name := actionName - if name == "" { - name = actionType - } - - return parsedAction{name: name, message: m}, nil -} - -// parseActionFromProto deserializes a single binary protobuf-encoded action. -func parseActionFromProto(b []byte) (parsedAction, error) { - base := &api.Base{} - if err := proto.Unmarshal(b, base); err != nil { - return parsedAction{}, err - } - - actionType := base.GetMetadata().GetType() - actionName := base.GetMetadata().GetName() - - mt, err := protoregistry.GlobalTypes.FindMessageByURL(actionType) - if err != nil { - return parsedAction{}, fmt.Errorf("lookup action type %q: %w", actionType, err) - } - - m := mt.New().Interface() - if err := proto.Unmarshal(b, m); err != nil { - return parsedAction{}, fmt.Errorf("unmarshal action %q: %w", actionType, err) - } - - // Use the action name if available, otherwise fall back to the type URL. - name := actionName - if name == "" { - name = actionType - } - - return parsedAction{name: name, message: m}, nil -} diff --git a/pkg/cmd/apply/parse.go b/pkg/cmd/apply/parse.go new file mode 100644 index 0000000..dcb961d --- /dev/null +++ b/pkg/cmd/apply/parse.go @@ -0,0 +1,125 @@ +package apply + +import ( + "bytes" + "encoding/json" + "fmt" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoregistry" + + "github.com/Azure/AKSFlexNode/components/api" +) + +// isJSONContent reports whether input appears to be a JSON object or array. +// It inspects the first non-whitespace byte: JSON objects begin with '{' and +// JSON arrays begin with '['; binary protobuf never starts with either byte. +func isJSONContent(input []byte) bool { + for _, b := range input { + if b == ' ' || b == '\t' || b == '\r' || b == '\n' { + continue + } + return b == '{' || b == '[' + } + return false +} + +// parseActions detects the input format and returns the pre-parsed actions. +func parseActions(input []byte) ([]parsedAction, error) { + if isJSONContent(input) { + return parseActionFromJSON(input) + } + + pa, err := parseActionFromProto(input) + if err != nil { + return nil, err + } + return []parsedAction{pa}, nil +} + +// parseActionFromJSON deserializes one or more JSON-encoded actions. The input +// may be a single JSON object or a JSON array of objects. +func parseActionFromJSON(input []byte) ([]parsedAction, error) { + tok, err := json.NewDecoder(bytes.NewBuffer(input)).Token() + if err != nil { + return nil, err + } + + var bs []json.RawMessage + if tok == json.Delim('[') { + if err := json.Unmarshal(input, &bs); err != nil { + return nil, err + } + } else { + bs = append(bs, input) + } + + // Pre-parse all actions so we know the total count and names up front. + parsed := make([]parsedAction, 0, len(bs)) + for _, b := range bs { + pa, err := parseAction(b) + if err != nil { + return nil, err + } + parsed = append(parsed, pa) + } + return parsed, nil +} + +func parseAction(b []byte) (parsedAction, error) { + base := &api.Base{} + if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(b, base); err != nil { + return parsedAction{}, err + } + + actionType := base.GetMetadata().GetType() + actionName := base.GetMetadata().GetName() + + mt, err := protoregistry.GlobalTypes.FindMessageByURL(actionType) + if err != nil { + return parsedAction{}, fmt.Errorf("lookup action type %q: %w", actionType, err) + } + + m := mt.New().Interface() + if err := protojson.Unmarshal(b, m); err != nil { + return parsedAction{}, fmt.Errorf("unmarshal action %q: %w", actionType, err) + } + + // Use the action name if available, otherwise fall back to the type URL. + name := actionName + if name == "" { + name = actionType + } + + return parsedAction{name: name, message: m}, nil +} + +// parseActionFromProto deserializes a single binary protobuf-encoded action. +func parseActionFromProto(b []byte) (parsedAction, error) { + base := &api.Base{} + if err := proto.Unmarshal(b, base); err != nil { + return parsedAction{}, err + } + + actionType := base.GetMetadata().GetType() + actionName := base.GetMetadata().GetName() + + mt, err := protoregistry.GlobalTypes.FindMessageByURL(actionType) + if err != nil { + return parsedAction{}, fmt.Errorf("lookup action type %q: %w", actionType, err) + } + + m := mt.New().Interface() + if err := proto.Unmarshal(b, m); err != nil { + return parsedAction{}, fmt.Errorf("unmarshal action %q: %w", actionType, err) + } + + // Use the action name if available, otherwise fall back to the type URL. + name := actionName + if name == "" { + name = actionType + } + + return parsedAction{name: name, message: m}, nil +} From 494e582b9664bf41728638e741b298fcdbe47723 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:18:43 +0000 Subject: [PATCH 5/5] apply: rename apply_test.go to parse_test.go Co-authored-by: bcho <1975118+bcho@users.noreply.github.com> --- pkg/cmd/apply/{apply_test.go => parse_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/cmd/apply/{apply_test.go => parse_test.go} (100%) diff --git a/pkg/cmd/apply/apply_test.go b/pkg/cmd/apply/parse_test.go similarity index 100% rename from pkg/cmd/apply/apply_test.go rename to pkg/cmd/apply/parse_test.go