Skip to content

Commit

Permalink
relocate mermaid channel writer (#987)
Browse files Browse the repository at this point in the history
Signed-off-by: Jordan Keister <[email protected]>
  • Loading branch information
grokspawn authored Jul 26, 2022
1 parent 05b1edc commit 524a11c
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 30 deletions.
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
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

0 comments on commit 524a11c

Please sign in to comment.