diff --git a/cmd/compose/alpha.go b/cmd/compose/alpha.go
index cb4181cd5..a05210d51 100644
--- a/cmd/compose/alpha.go
+++ b/cmd/compose/alpha.go
@@ -34,7 +34,7 @@ func alphaCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
cmd.AddCommand(
watchCommand(p, backend),
dryRunRedirectCommand(p),
- vizCommand(p),
+ vizCommand(p, backend),
)
return cmd
}
diff --git a/cmd/compose/viz.go b/cmd/compose/viz.go
index cb8fce12a..145340b7a 100644
--- a/cmd/compose/viz.go
+++ b/cmd/compose/viz.go
@@ -20,10 +20,9 @@ import (
"context"
"fmt"
"os"
- "strconv"
"strings"
- "github.com/compose-spec/compose-go/types"
+ "github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
@@ -35,10 +34,7 @@ type vizOptions struct {
indentationStr string
}
-// maps a service with the services it depends on
-type vizGraph map[*types.ServiceConfig][]*types.ServiceConfig
-
-func vizCommand(p *ProjectOptions) *cobra.Command {
+func vizCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
opts := vizOptions{
ProjectOptions: p,
}
@@ -54,7 +50,7 @@ func vizCommand(p *ProjectOptions) *cobra.Command {
return err
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
- return runViz(ctx, &opts)
+ return runViz(ctx, backend, &opts)
}),
}
@@ -66,7 +62,7 @@ func vizCommand(p *ProjectOptions) *cobra.Command {
return cmd
}
-func runViz(_ context.Context, opts *vizOptions) error {
+func runViz(ctx context.Context, backend api.Service, opts *vizOptions) error {
_, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL")
project, err := opts.ToProject(nil)
if err != nil {
@@ -74,110 +70,18 @@ func runViz(_ context.Context, opts *vizOptions) error {
}
// build graph
- graph := make(vizGraph)
- for i, serviceConfig := range project.Services {
- serviceConfigPtr := &project.Services[i]
- graph[serviceConfigPtr] = make([]*types.ServiceConfig, 0, len(serviceConfig.DependsOn))
- for dependencyName := range serviceConfig.DependsOn {
- // no error should be returned since dependencyName should exist
- dependency, _ := project.GetService(dependencyName)
- graph[serviceConfigPtr] = append(graph[serviceConfigPtr], &dependency)
- }
- }
+ graphStr, _ := backend.Viz(ctx, project, api.VizOptions{
+ IncludeNetworks: opts.includeNetworks,
+ IncludePorts: opts.includePorts,
+ IncludeImageName: opts.includeImageName,
+ Indentation: opts.indentationStr,
+ })
- // build graphviz graph
- var graphBuilder strings.Builder
- graphBuilder.WriteString("digraph " + project.Name + " {\n")
- graphBuilder.WriteString(opts.indentationStr + "layout=dot;\n")
- addNodes(&graphBuilder, graph, opts)
- graphBuilder.WriteByte('\n')
- addEdges(&graphBuilder, graph, opts)
- graphBuilder.WriteString("}\n")
-
- fmt.Println(graphBuilder.String())
+ fmt.Println(graphStr)
return nil
}
-// addNodes adds the corresponding graphviz representation of all the nodes in the given graph to the graphBuilder
-// returns the same graphBuilder
-func addNodes(graphBuilder *strings.Builder, graph vizGraph, opts *vizOptions) *strings.Builder {
- for serviceNode := range graph {
- // write:
- // "service name" [style="filled" label<service name
- graphBuilder.WriteString(opts.indentationStr)
- writeQuoted(graphBuilder, serviceNode.Name)
- graphBuilder.WriteString(" [style=\"filled\" label=<")
- graphBuilder.WriteString(serviceNode.Name)
- graphBuilder.WriteString("")
-
- if opts.includeNetworks && len(serviceNode.Networks) > 0 {
- graphBuilder.WriteString("")
- graphBuilder.WriteString("
Networks:")
- for _, networkName := range serviceNode.NetworksByPriority() {
- graphBuilder.WriteString("
")
- graphBuilder.WriteString(networkName)
- }
- graphBuilder.WriteString("")
- }
-
- if opts.includePorts && len(serviceNode.Ports) > 0 {
- graphBuilder.WriteString("")
- graphBuilder.WriteString("
Ports:")
- for _, portConfig := range serviceNode.Ports {
- graphBuilder.WriteString("
")
- if len(portConfig.HostIP) > 0 {
- graphBuilder.WriteString(portConfig.HostIP)
- graphBuilder.WriteByte(':')
- }
- graphBuilder.WriteString(portConfig.Published)
- graphBuilder.WriteByte(':')
- graphBuilder.WriteString(strconv.Itoa(int(portConfig.Target)))
- graphBuilder.WriteString(" (")
- graphBuilder.WriteString(portConfig.Protocol)
- graphBuilder.WriteString(", ")
- graphBuilder.WriteString(portConfig.Mode)
- graphBuilder.WriteString(")")
- }
- graphBuilder.WriteString("")
- }
-
- if opts.includeImageName {
- graphBuilder.WriteString("")
- graphBuilder.WriteString("
Image:
")
- graphBuilder.WriteString(serviceNode.Image)
- graphBuilder.WriteString("")
- }
-
- graphBuilder.WriteString(">];\n")
- }
-
- return graphBuilder
-}
-
-// addEdges adds the corresponding graphviz representation of all edges in the given graph to the graphBuilder
-// returns the same graphBuilder
-func addEdges(graphBuilder *strings.Builder, graph vizGraph, opts *vizOptions) *strings.Builder {
- for parent, children := range graph {
- for _, child := range children {
- graphBuilder.WriteString(opts.indentationStr)
- writeQuoted(graphBuilder, parent.Name)
- graphBuilder.WriteString(" -> ")
- writeQuoted(graphBuilder, child.Name)
- graphBuilder.WriteString(";\n")
- }
- }
-
- return graphBuilder
-}
-
-// writeQuoted writes "str" to builder
-func writeQuoted(builder *strings.Builder, str string) {
- builder.WriteByte('"')
- builder.WriteString(str)
- builder.WriteByte('"')
-}
-
// preferredIndentationStr returns a single string given the indentation preference
func preferredIndentationStr(size int, useSpace bool) (string, error) {
if size < 0 {
diff --git a/cmd/compose/viz_test.go b/cmd/compose/viz_test.go
new file mode 100644
index 000000000..9691e1db3
--- /dev/null
+++ b/cmd/compose/viz_test.go
@@ -0,0 +1,92 @@
+/*
+ Copyright 2020 Docker Compose CLI authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package compose
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPreferredIndentationStr(t *testing.T) {
+ type args struct {
+ size int
+ useSpace bool
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ wantErr bool
+ }{
+ {
+ name: "should return '\\t\\t'",
+ args: args{
+ size: 2,
+ useSpace: false,
+ },
+ want: "\t\t",
+ wantErr: false,
+ },
+ {
+ name: "should return ' '",
+ args: args{
+ size: 4,
+ useSpace: true,
+ },
+ want: " ",
+ wantErr: false,
+ },
+ {
+ name: "should return ''",
+ args: args{
+ size: 0,
+ useSpace: false,
+ },
+ want: "",
+ wantErr: false,
+ },
+ {
+ name: "should return ''",
+ args: args{
+ size: 0,
+ useSpace: true,
+ },
+ want: "",
+ wantErr: false,
+ },
+ {
+ name: "should throw error because indentation size < 0",
+ args: args{
+ size: -1,
+ useSpace: false,
+ },
+ want: "",
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := preferredIndentationStr(tt.args.size, tt.args.useSpace)
+ if tt.wantErr && assert.NotNilf(t, err, fmt.Sprintf("preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)) {
+ return
+ }
+ assert.Equalf(t, tt.want, got, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
+ })
+ }
+}
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 1a6f7e464..868bf1165 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -82,6 +82,19 @@ type Service interface {
DryRunMode(ctx context.Context, dryRun bool) (context.Context, error)
// Watch services' development context and sync/notify/rebuild/restart on changes
Watch(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
+ // Viz generates a graphviz graph of the project services
+ Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error)
+}
+
+type VizOptions struct {
+ // IncludeNetworks if true, network names a container is attached to should appear in the graph node
+ IncludeNetworks bool
+ // IncludePorts if true, ports a container exposes should appear in the graph node
+ IncludePorts bool
+ // IncludeImageName if true, name of the image used to create a container should appear in the graph node
+ IncludeImageName bool
+ // Indentation string to be used to indent graphviz code, e.g. "\t", " "
+ Indentation string
}
// WatchOptions group options of the Watch API
diff --git a/pkg/api/proxy.go b/pkg/api/proxy.go
index 621bce79a..30f9aa273 100644
--- a/pkg/api/proxy.go
+++ b/pkg/api/proxy.go
@@ -53,6 +53,7 @@ type ServiceProxy struct {
WatchFn func(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
MaxConcurrencyFn func(parallel int)
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
+ VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
interceptors []Interceptor
}
@@ -93,6 +94,7 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
s.WatchFn = service.Watch
s.MaxConcurrencyFn = service.MaxConcurrency
s.DryRunModeFn = service.DryRunMode
+ s.VizFn = service.Viz
return s
}
@@ -323,6 +325,14 @@ func (s *ServiceProxy) Watch(ctx context.Context, project *types.Project, servic
return s.WatchFn(ctx, project, services, options)
}
+// Viz implements Viz interface
+func (s *ServiceProxy) Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error) {
+ if s.VizFn == nil {
+ return "", ErrNotImplemented
+ }
+ return s.VizFn(ctx, project, options)
+}
+
func (s *ServiceProxy) MaxConcurrency(i int) {
s.MaxConcurrencyFn(i)
}
diff --git a/pkg/compose/viz.go b/pkg/compose/viz.go
new file mode 100644
index 000000000..4d932ee44
--- /dev/null
+++ b/pkg/compose/viz.go
@@ -0,0 +1,132 @@
+/*
+ Copyright 2023 Docker Compose CLI authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package compose
+
+import (
+ "context"
+ "strconv"
+ "strings"
+
+ "github.com/compose-spec/compose-go/types"
+ "github.com/docker/compose/v2/pkg/api"
+)
+
+// maps a service with the services it depends on
+type vizGraph map[*types.ServiceConfig][]*types.ServiceConfig
+
+func (s *composeService) Viz(_ context.Context, project *types.Project, opts api.VizOptions) (string, error) {
+ graph := make(vizGraph)
+ for i, serviceConfig := range project.Services {
+ serviceConfigPtr := &project.Services[i]
+ graph[serviceConfigPtr] = make([]*types.ServiceConfig, 0, len(serviceConfig.DependsOn))
+ for dependencyName := range serviceConfig.DependsOn {
+ // no error should be returned since dependencyName should exist
+ dependency, _ := project.GetService(dependencyName)
+ graph[serviceConfigPtr] = append(graph[serviceConfigPtr], &dependency)
+ }
+ }
+
+ // build graphviz graph
+ var graphBuilder strings.Builder
+ graphBuilder.WriteString("digraph " + project.Name + " {\n")
+ graphBuilder.WriteString(opts.Indentation + "layout=dot;\n")
+ addNodes(&graphBuilder, graph, &opts)
+ graphBuilder.WriteByte('\n')
+ addEdges(&graphBuilder, graph, &opts)
+ graphBuilder.WriteString("}\n")
+
+ return graphBuilder.String(), nil
+}
+
+// addNodes adds the corresponding graphviz representation of all the nodes in the given graph to the graphBuilder
+// returns the same graphBuilder
+func addNodes(graphBuilder *strings.Builder, graph vizGraph, opts *api.VizOptions) *strings.Builder {
+ for serviceNode := range graph {
+ // write:
+ // "service name" [style="filled" label<service name
+ graphBuilder.WriteString(opts.Indentation)
+ writeQuoted(graphBuilder, serviceNode.Name)
+ graphBuilder.WriteString(" [style=\"filled\" label=<")
+ graphBuilder.WriteString(serviceNode.Name)
+ graphBuilder.WriteString("")
+
+ if opts.IncludeNetworks && len(serviceNode.Networks) > 0 {
+ graphBuilder.WriteString("")
+ graphBuilder.WriteString("
Networks:")
+ for _, networkName := range serviceNode.NetworksByPriority() {
+ graphBuilder.WriteString("
")
+ graphBuilder.WriteString(networkName)
+ }
+ graphBuilder.WriteString("")
+ }
+
+ if opts.IncludePorts && len(serviceNode.Ports) > 0 {
+ graphBuilder.WriteString("")
+ graphBuilder.WriteString("
Ports:")
+ for _, portConfig := range serviceNode.Ports {
+ graphBuilder.WriteString("
")
+ if len(portConfig.HostIP) > 0 {
+ graphBuilder.WriteString(portConfig.HostIP)
+ graphBuilder.WriteByte(':')
+ }
+ graphBuilder.WriteString(portConfig.Published)
+ graphBuilder.WriteByte(':')
+ graphBuilder.WriteString(strconv.Itoa(int(portConfig.Target)))
+ graphBuilder.WriteString(" (")
+ graphBuilder.WriteString(portConfig.Protocol)
+ graphBuilder.WriteString(", ")
+ graphBuilder.WriteString(portConfig.Mode)
+ graphBuilder.WriteString(")")
+ }
+ graphBuilder.WriteString("")
+ }
+
+ if opts.IncludeImageName {
+ graphBuilder.WriteString("")
+ graphBuilder.WriteString("
Image:
")
+ graphBuilder.WriteString(serviceNode.Image)
+ graphBuilder.WriteString("")
+ }
+
+ graphBuilder.WriteString(">];\n")
+ }
+
+ return graphBuilder
+}
+
+// addEdges adds the corresponding graphviz representation of all edges in the given graph to the graphBuilder
+// returns the same graphBuilder
+func addEdges(graphBuilder *strings.Builder, graph vizGraph, opts *api.VizOptions) *strings.Builder {
+ for parent, children := range graph {
+ for _, child := range children {
+ graphBuilder.WriteString(opts.Indentation)
+ writeQuoted(graphBuilder, parent.Name)
+ graphBuilder.WriteString(" -> ")
+ writeQuoted(graphBuilder, child.Name)
+ graphBuilder.WriteString(";\n")
+ }
+ }
+
+ return graphBuilder
+}
+
+// writeQuoted writes "str" to builder
+func writeQuoted(builder *strings.Builder, str string) {
+ builder.WriteByte('"')
+ builder.WriteString(str)
+ builder.WriteByte('"')
+}
diff --git a/pkg/compose/viz_test.go b/pkg/compose/viz_test.go
new file mode 100644
index 000000000..15425b904
--- /dev/null
+++ b/pkg/compose/viz_test.go
@@ -0,0 +1,204 @@
+/*
+ Copyright 2020 Docker Compose CLI authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package compose
+
+import (
+ "context"
+ "strconv"
+ "testing"
+
+ "github.com/compose-spec/compose-go/types"
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/assert"
+
+ compose "github.com/docker/compose/v2/pkg/api"
+ "github.com/docker/compose/v2/pkg/mocks"
+)
+
+func TestViz(t *testing.T) {
+ project := types.Project{
+ Name: "viz-test",
+ WorkingDir: "/home",
+ Services: []types.ServiceConfig{
+ {
+ Name: "service1",
+ Image: "image-for-service1",
+ Ports: []types.ServicePortConfig{
+ {
+ Published: "80",
+ Target: 80,
+ Protocol: "tcp",
+ },
+ {
+ Published: "53",
+ Target: 533,
+ Protocol: "udp",
+ },
+ },
+ Networks: map[string]*types.ServiceNetworkConfig{
+ "internal": nil,
+ },
+ },
+ {
+ Name: "service2",
+ Image: "image-for-service2",
+ Ports: []types.ServicePortConfig{},
+ },
+ {
+ Name: "service3",
+ Image: "some-image",
+ DependsOn: map[string]types.ServiceDependency{
+ "service2": {},
+ "service1": {},
+ },
+ },
+ {
+ Name: "service4",
+ Image: "another-image",
+ DependsOn: map[string]types.ServiceDependency{
+ "service3": {},
+ },
+ Ports: []types.ServicePortConfig{
+ {
+ Published: "8080",
+ Target: 80,
+ },
+ },
+ Networks: map[string]*types.ServiceNetworkConfig{
+ "external": nil,
+ },
+ },
+ },
+ Networks: types.Networks{
+ "internal": types.NetworkConfig{},
+ "external": types.NetworkConfig{},
+ "not-used": types.NetworkConfig{},
+ },
+ Volumes: nil,
+ Secrets: nil,
+ Configs: nil,
+ Extensions: nil,
+ ComposeFiles: nil,
+ Environment: nil,
+ DisabledServices: nil,
+ Profiles: nil,
+ }
+
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+ cli := mocks.NewMockCli(mockCtrl)
+ tested := composeService{
+ dockerCli: cli,
+ }
+
+ ctx := context.Background()
+
+ t.Run("viz (no ports, networks or image)", func(t *testing.T) {
+ graphStr, err := tested.Viz(ctx, &project, compose.VizOptions{
+ Indentation: " ",
+ IncludePorts: false,
+ IncludeImageName: false,
+ IncludeNetworks: false,
+ })
+ assert.NoError(t, err, "viz command failed")
+
+ // check indentation
+ assert.Contains(t, graphStr, "\n ", graphStr)
+ assert.NotContains(t, graphStr, "\n ", graphStr)
+
+ // check digraph name
+ assert.Contains(t, graphStr, "digraph "+project.Name, graphStr)
+
+ // check nodes
+ for _, service := range project.Services {
+ assert.Contains(t, graphStr, "\""+service.Name+"\" [style=\"filled\"", graphStr)
+ }
+
+ // check node attributes
+ assert.NotContains(t, graphStr, "Networks", graphStr)
+ assert.NotContains(t, graphStr, "Image", graphStr)
+ assert.NotContains(t, graphStr, "Ports", graphStr)
+
+ // check edges that SHOULD exist in the generated graph
+ allowedEdges := make(map[string][]string)
+ for _, service := range project.Services {
+ allowedEdges[service.Name] = make([]string, 0, len(service.DependsOn))
+ for depName := range service.DependsOn {
+ allowedEdges[service.Name] = append(allowedEdges[service.Name], depName)
+ }
+ }
+ for serviceName, dependencies := range allowedEdges {
+ for _, dependencyName := range dependencies {
+ assert.Contains(t, graphStr, "\""+serviceName+"\" -> \""+dependencyName+"\"", graphStr)
+ }
+ }
+
+ // check edges that SHOULD NOT exist in the generated graph
+ forbiddenEdges := make(map[string][]string)
+ for _, service := range project.Services {
+ forbiddenEdges[service.Name] = make([]string, 0, len(project.ServiceNames())-len(service.DependsOn))
+ for _, serviceName := range project.ServiceNames() {
+ _, edgeExists := service.DependsOn[serviceName]
+ if !edgeExists {
+ forbiddenEdges[service.Name] = append(forbiddenEdges[service.Name], serviceName)
+ }
+ }
+ }
+ for serviceName, forbiddenDeps := range forbiddenEdges {
+ for _, forbiddenDep := range forbiddenDeps {
+ assert.NotContains(t, graphStr, "\""+serviceName+"\" -> \""+forbiddenDep+"\"")
+ }
+ }
+ })
+
+ t.Run("viz (with ports, networks and image)", func(t *testing.T) {
+ graphStr, err := tested.Viz(ctx, &project, compose.VizOptions{
+ Indentation: "\t",
+ IncludePorts: true,
+ IncludeImageName: true,
+ IncludeNetworks: true,
+ })
+ assert.NoError(t, err, "viz command failed")
+
+ // check indentation
+ assert.Contains(t, graphStr, "\n\t", graphStr)
+ assert.NotContains(t, graphStr, "\n\t\t", graphStr)
+
+ // check digraph name
+ assert.Contains(t, graphStr, "digraph "+project.Name, graphStr)
+
+ // check nodes
+ for _, service := range project.Services {
+ assert.Contains(t, graphStr, "\""+service.Name+"\" [style=\"filled\"", graphStr)
+ }
+
+ // check node attributes
+ assert.Contains(t, graphStr, "Networks", graphStr)
+ assert.Contains(t, graphStr, ">internal<", graphStr)
+ assert.Contains(t, graphStr, ">external<", graphStr)
+ assert.Contains(t, graphStr, "Image", graphStr)
+ for _, service := range project.Services {
+ assert.Contains(t, graphStr, ">"+service.Image+"<", graphStr)
+ }
+ assert.Contains(t, graphStr, "Ports", graphStr)
+ for _, service := range project.Services {
+ for _, portConfig := range service.Ports {
+ assert.NotContains(t, graphStr, ">"+portConfig.Published+":"+strconv.Itoa(int(portConfig.Target))+"<", graphStr)
+ }
+ }
+ })
+}
diff --git a/pkg/mocks/mock_docker_compose_api.go b/pkg/mocks/mock_docker_compose_api.go
index 205f39557..120affd98 100644
--- a/pkg/mocks/mock_docker_compose_api.go
+++ b/pkg/mocks/mock_docker_compose_api.go
@@ -408,6 +408,21 @@ func (mr *MockServiceMockRecorder) Up(ctx, project, options interface{}) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Up", reflect.TypeOf((*MockService)(nil).Up), ctx, project, options)
}
+// Viz mocks base method.
+func (m *MockService) Viz(ctx context.Context, project *types.Project, options api.VizOptions) (string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Viz", ctx, project, options)
+ ret0, _ := ret[0].(string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Viz indicates an expected call of Viz.
+func (mr *MockServiceMockRecorder) Viz(ctx, project, options interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Viz", reflect.TypeOf((*MockService)(nil).Viz), ctx, project, options)
+}
+
// Watch mocks base method.
func (m *MockService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error {
m.ctrl.T.Helper()