Skip to content

Commit

Permalink
cmd: Introduce HUBBLE_COMPAT=legacy-json-output
Browse files Browse the repository at this point in the history
[ upstream commit 221b8ea ]

This commit introduces a new environment variable, `HUBBLE_COMPAT`,
which can be used to restore old behavior in the presence of breaking
changes. It's inspired by Golang's GODEBUG variable documented here:
https://pkg.go.dev/runtime

The intent of this flag is to allow us introduce breaking changes in the
CLI, while still preserving backwards-compatibility with older versions
where needed. As an example, this commit also bring back the legacy `-o
json` flow schema if `HUBBLE_COMPAT` is set to `legacy-json-output`.
This allows older Cilium stable versions to pull in a newer version of
the Hubble CLI as part of security updates, while preserving the
behavior of the Hubble CLI that originally shipped with that Cilium
version.

HUBBLE_COMPAT options are _not_ intended to be supported indefinitely.
Rather, compat options may be removed once the ecosystem (such as
downstream users like the Cilium-Agent image) has caught up.

We use an environment variable instead of a viper flag to ensure that we
could also use it to modify the behavior of viper or pflags if needed in
the future.

Signed-off-by: Sebastian Wicki <sebastian@isovalent.com>
  • Loading branch information
gandro committed Jan 24, 2023
1 parent 67079c4 commit 70fb65a
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 4 deletions.
44 changes: 44 additions & 0 deletions cmd/common/config/compat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Hubble

package config

import (
"os"
"strings"
)

const (
// HUBBLE_COMPAT is an environment variable similar to GODEBUG.
//
// It allows us to preserve old CLI behavior in the presence of
// breaking changes.
compatEnvKey = "HUBBLE_COMPAT"

// legacy-json-output uses the old "-o json" format present
// in Hubble CLI v0.10 and older
compatLegacyJSONOutput = "legacy-json-output"
)

// CompatOptions defines the available compatibility options
type CompatOptions struct {
LegacyJSONOutput bool
}

// Compat contains the parsed HUBBLE_COMPAT options
var Compat = compatFromEnv()

func compatFromEnv() CompatOptions {
c := CompatOptions{}

for _, opt := range strings.Split(os.Getenv(compatEnvKey), ",") {
switch strings.ToLower(opt) {
case compatLegacyJSONOutput:
c.LegacyJSONOutput = true
default:
// silently ignore unknown options for forward-compatibility
}
}

return c
}
9 changes: 8 additions & 1 deletion cmd/observe/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package observe
import (
"fmt"

"github.com/cilium/hubble/cmd/common/config"
hubprinter "github.com/cilium/hubble/pkg/printer"
hubtime "github.com/cilium/hubble/pkg/time"
)
Expand All @@ -21,7 +22,13 @@ func handleEventsArgs(debug bool) error {
opts = append(opts, hubprinter.Compact())
case "dict":
opts = append(opts, hubprinter.Dict())
case "json", "JSON", "jsonpb":
case "json", "JSON":
if config.Compat.LegacyJSONOutput {
opts = append(opts, hubprinter.JSONLegacy())
break
}
fallthrough
case "jsonpb":
opts = append(opts, hubprinter.JSONPB())
case "tab", "table":
if selectorOpts.follow {
Expand Down
8 changes: 7 additions & 1 deletion cmd/observe/flows.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,13 @@ func handleFlowArgs(ofilter *flowFilter, debug bool) (err error) {
opts = append(opts, hubprinter.Compact())
case "dict":
opts = append(opts, hubprinter.Dict())
case "json", "JSON", "jsonpb":
case "json", "JSON":
if config.Compat.LegacyJSONOutput {
opts = append(opts, hubprinter.JSONLegacy())
break
}
fallthrough
case "jsonpb":
opts = append(opts, hubprinter.JSONPB())
case "tab", "table":
if selectorOpts.follow {
Expand Down
11 changes: 10 additions & 1 deletion pkg/printer/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type Output int
const (
// TabOutput prints flows in even tab-aligned columns.
TabOutput Output = iota
// JSONLegacyOutput prints flows as json in the legacy format
JSONLegacyOutput
// CompactOutput prints flows as compact as possible (similar to monitor).
CompactOutput
// DictOutput presents the same information as TabOutput, but each flow is
Expand All @@ -37,6 +39,13 @@ type Options struct {
// Option ...
type Option func(*Options)

// JSONLegacy encoded output from the printer.
func JSONLegacy() Option {
return func(opts *Options) {
opts.output = JSONLegacyOutput
}
}

// JSONPB encodes GetFlowsResponse as JSON according to proto3's JSON mapping.
func JSONPB() Option {
return func(opts *Options) {
Expand Down Expand Up @@ -116,7 +125,7 @@ func WithNodeName() Option {
}

// WithTimeFormat specifies the time format layout to use when printing out
// timestamps. This option has no effect if JSON or JSONPB option is used.
// timestamps. This option has no effect if JSONLegacy or JSONPB option is used.
// The layout must be a time format layout as specified in the standard
// library's time package.
func WithTimeFormat(layout string) Option {
Expand Down
8 changes: 7 additions & 1 deletion pkg/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func New(fopts ...Option) *Printer {
// initialize tabwriter since it's going to be needed
p.tw = tabwriter.NewWriter(opts.w, 2, 0, 3, ' ', 0)
p.color.disable() // the tabwriter is not compatible with colors, thus disable coloring
case JSONPBOutput:
case JSONLegacyOutput, JSONPBOutput:
p.jsonEncoder = json.NewEncoder(p.opts.w)
}

Expand Down Expand Up @@ -361,6 +361,8 @@ func (p *Printer) WriteProtoFlow(res *observerpb.GetFlowsResponse) error {
if err != nil {
return fmt.Errorf("failed to write out packet: %v", err)
}
case JSONLegacyOutput:
return p.jsonEncoder.Encode(f)
case JSONPBOutput:
return p.jsonEncoder.Encode(res)
}
Expand Down Expand Up @@ -561,6 +563,8 @@ func (p *Printer) WriteProtoAgentEvent(r *observerpb.GetAgentEventsResponse) err
}

switch p.opts.output {
case JSONLegacyOutput:
return p.jsonEncoder.Encode(e)
case JSONPBOutput:
return p.jsonEncoder.Encode(r)
case DictOutput:
Expand Down Expand Up @@ -661,6 +665,8 @@ func (p *Printer) WriteProtoDebugEvent(r *observerpb.GetDebugEventsResponse) err
}

switch p.opts.output {
case JSONLegacyOutput:
return p.jsonEncoder.Encode(e)
case JSONPBOutput:
return p.jsonEncoder.Encode(r)
case DictOutput:
Expand Down

0 comments on commit 70fb65a

Please sign in to comment.