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
81 changes: 81 additions & 0 deletions alpha/declcfg/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,94 @@ package declcfg
import (
"bytes"
"encoding/json"
"fmt"
"io"
"sort"
"strings"

"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/yaml"
)

// writes out the channel edges of the declarative config graph in a mermaid format capable of being pasted into
// mermaid renderers like github, mermaid.live, etc.
// output is sorted lexicographically by package name, and then by channel name
//
// NB: Output has wrapper comments stating the skipRange edge caveat in HTML comment format, which cannot be parsed by mermaid renderers.
// This is deliberate, and intended as an explicit acknowledgement of the limitations, instead of requiring the user to notice the missing edges upon inspection.
//
// Example output:
// <!-- PLEASE NOTE: skipRange edges are not currently displayed -->
// graph LR
// %% package "neuvector-certified-operator-rhmp"
// subgraph "neuvector-certified-operator-rhmp"
// %% channel "beta"
// subgraph neuvector-certified-operator-rhmp-beta["beta"]
// neuvector-certified-operator-rhmp-beta-neuvector-operator.v1.2.8["neuvector-operator.v1.2.8"]
// neuvector-certified-operator-rhmp-beta-neuvector-operator.v1.2.9["neuvector-operator.v1.2.9"]
// neuvector-certified-operator-rhmp-beta-neuvector-operator.v1.3.0["neuvector-operator.v1.3.0"]
// neuvector-certified-operator-rhmp-beta-neuvector-operator.v1.3.0["neuvector-operator.v1.3.0"]-- replaces --> neuvector-certified-operator-rhmp-beta-neuvector-operator.v1.2.8["neuvector-operator.v1.2.8"]
// neuvector-certified-operator-rhmp-beta-neuvector-operator.v1.3.0["neuvector-operator.v1.3.0"]-- skips --> neuvector-certified-operator-rhmp-beta-neuvector-operator.v1.2.9["neuvector-operator.v1.2.9"]
// end
// end
// end
// <!-- PLEASE NOTE: skipRange edges are not currently displayed -->
func WriteMermaidChannels(cfg DeclarativeConfig, out io.Writer) error {
pkgs := map[string]*strings.Builder{}

sort.Slice(cfg.Channels, func(i, j int) bool {
return cfg.Channels[i].Name < cfg.Channels[j].Name
})

for _, c := range cfg.Channels {
pkgBuilder, ok := pkgs[c.Package]
if !ok {
pkgBuilder = &strings.Builder{}
pkgs[c.Package] = pkgBuilder
}
channelID := fmt.Sprintf("%s-%s", c.Package, c.Name)
pkgBuilder.WriteString(fmt.Sprintf(" %%%% channel %q\n", c.Name))
pkgBuilder.WriteString(fmt.Sprintf(" subgraph %s[%q]\n", channelID, c.Name))

for _, ce := range c.Entries {
entryId := fmt.Sprintf("%s-%s", channelID, ce.Name)
pkgBuilder.WriteString(fmt.Sprintf(" %s[%q]\n", entryId, ce.Name))

// no support for SkipRange yet
if len(ce.Replaces) > 0 {
replacesId := fmt.Sprintf("%s-%s", channelID, ce.Replaces)
pkgBuilder.WriteString(fmt.Sprintf(" %s[%q]-- %s --> %s[%q]\n", entryId, ce.Name, "replaces", replacesId, ce.Replaces))
}
if len(ce.Skips) > 0 {
for _, s := range ce.Skips {
skipsId := fmt.Sprintf("%s-%s", channelID, s)
pkgBuilder.WriteString(fmt.Sprintf(" %s[%q]-- %s --> %s[%q]\n", entryId, ce.Name, "skips", skipsId, s))
}
}
}
pkgBuilder.WriteString(" end\n")
}

out.Write([]byte("<!-- PLEASE NOTE: skipRange edges are not currently displayed -->\n"))
out.Write([]byte("graph LR\n"))
pkgNames := []string{}
for pname, _ := range pkgs {
pkgNames = append(pkgNames, pname)
}
sort.Slice(pkgNames, func(i, j int) bool {
return pkgNames[i] < pkgNames[j]
})
for _, pkgName := range pkgNames {
out.Write([]byte(fmt.Sprintf(" %%%% package %q\n", pkgName)))
out.Write([]byte(fmt.Sprintf(" subgraph %q\n", pkgName)))
out.Write([]byte(pkgs[pkgName].String()))
out.Write([]byte(" end\n"))
}
out.Write([]byte("<!-- PLEASE NOTE: skipRange edges are not currently displayed -->\n"))

return nil
}

func WriteJSON(cfg DeclarativeConfig, w io.Writer) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
Expand Down
53 changes: 53 additions & 0 deletions alpha/declcfg/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,3 +469,56 @@ func removeJSONWhitespace(cfg *DeclarativeConfig) {
cfg.Others[io].Blob = buf.Bytes()
}
}

func TestWriteMermaidChannels(t *testing.T) {
type spec struct {
name string
cfg DeclarativeConfig
expected string
}
specs := []spec{
{
name: "Success",
cfg: buildValidDeclarativeConfig(true),
expected: `<!-- PLEASE NOTE: skipRange edges are not currently displayed -->
graph LR
%% package "anakin"
subgraph "anakin"
%% channel "dark"
subgraph anakin-dark["dark"]
anakin-dark-anakin.v0.0.1["anakin.v0.0.1"]
anakin-dark-anakin.v0.1.0["anakin.v0.1.0"]
anakin-dark-anakin.v0.1.0["anakin.v0.1.0"]-- replaces --> anakin-dark-anakin.v0.0.1["anakin.v0.0.1"]
anakin-dark-anakin.v0.1.1["anakin.v0.1.1"]
anakin-dark-anakin.v0.1.1["anakin.v0.1.1"]-- replaces --> anakin-dark-anakin.v0.0.1["anakin.v0.0.1"]
anakin-dark-anakin.v0.1.1["anakin.v0.1.1"]-- skips --> anakin-dark-anakin.v0.1.0["anakin.v0.1.0"]
end
%% channel "light"
subgraph anakin-light["light"]
anakin-light-anakin.v0.0.1["anakin.v0.0.1"]
anakin-light-anakin.v0.1.0["anakin.v0.1.0"]
anakin-light-anakin.v0.1.0["anakin.v0.1.0"]-- replaces --> anakin-light-anakin.v0.0.1["anakin.v0.0.1"]
end
end
%% package "boba-fett"
subgraph "boba-fett"
%% channel "mando"
subgraph boba-fett-mando["mando"]
boba-fett-mando-boba-fett.v1.0.0["boba-fett.v1.0.0"]
boba-fett-mando-boba-fett.v2.0.0["boba-fett.v2.0.0"]
boba-fett-mando-boba-fett.v2.0.0["boba-fett.v2.0.0"]-- replaces --> boba-fett-mando-boba-fett.v1.0.0["boba-fett.v1.0.0"]
end
end
<!-- PLEASE NOTE: skipRange edges are not currently displayed -->
`,
},
}
for _, s := range specs {
t.Run(s.name, func(t *testing.T) {
var buf bytes.Buffer
err := WriteMermaidChannels(s.cfg, &buf)
require.NoError(t, err)
require.Equal(t, s.expected, buf.String())
})
}
}
27 changes: 0 additions & 27 deletions alpha/veneer/semver/semver.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package semver

import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"sort"

Expand Down Expand Up @@ -421,31 +419,6 @@ func getMinorVersion(v semver.Version) semver.Version {
}
}

func MermaidChannelWriter(cfg declcfg.DeclarativeConfig, out io.Writer) error {
for _, c := range cfg.Channels {
var buf bytes.Buffer

buf.WriteString(fmt.Sprintf("<-- Channel %q --> \n", c.Name))
buf.WriteString("graph LR\n")

for _, ce := range c.Entries {

// no support for SkipRange yet
buf.WriteString(fmt.Sprintf("%s\n", ce.Name))
if len(ce.Replaces) > 0 {
buf.WriteString(fmt.Sprintf("%s-- %s --> %s\n", ce.Name, "replaces", ce.Replaces))
}
if len(ce.Skips) > 0 {
for _, s := range ce.Skips {
buf.WriteString(fmt.Sprintf("%s-- %s --> %s\n", ce.Name, "skips", s))
}
}
}
out.Write(buf.Bytes())
}
return nil
}

func withoutBuildMetadataConflict(versions *map[string]semver.Version) error {
errs := []error{}

Expand Down
2 changes: 1 addition & 1 deletion cmd/opm/alpha/veneer/semver.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func newSemverCmd() *cobra.Command {
case "yaml":
write = declcfg.WriteYAML
case "mermaid":
write = semver.MermaidChannelWriter
write = declcfg.WriteMermaidChannels
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: Hmm, a write function that takes a config variable to determine output format might be nice.

default:
return fmt.Errorf("invalid output format %q", output)
}
Expand Down
6 changes: 4 additions & 2 deletions cmd/opm/render/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ func NewCmd() *cobra.Command {
write = declcfg.WriteYAML
case "json":
write = declcfg.WriteJSON
case "mermaid":
write = declcfg.WriteMermaidChannels
default:
log.Fatalf("invalid --output value %q, expected (json|yaml)", output)
log.Fatalf("invalid --output value %q, expected (json|yaml|mermaid)", output)
}

// The bundle loading impl is somewhat verbose, even on the happy path,
Expand Down Expand Up @@ -79,7 +81,7 @@ func NewCmd() *cobra.Command {
}
},
}
cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)")
cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml|mermaid)")
cmd.Flags().Bool("skip-tls-verify", false, "disable TLS verification")
cmd.Flags().Bool("use-http", false, "use plain HTTP")
return cmd
Expand Down