diff --git a/.gitignore b/.gitignore index 85d0bbc..3d21600 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,11 @@ bin/ lib/ +/out/* +/k8sviz.iml +service-account-key.json +config-k8sviz +/*.png +/merged.gv-e +/global.dot +/config-k8sviz.json +/service-account-key2.json diff --git a/Dockerfile b/Dockerfile index d0374a0..4031094 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,17 @@ COPY . . RUN make build FROM alpine:3.11 -RUN apk add --no-cache bash graphviz ttf-linux-libertine +RUN apk add --no-cache bash graphviz ttf-linux-libertine curl python m4 +# Downloading gcloud package +RUN curl https://dl.google.com/dl/cloudsdk/release/google-cloud-sdk.tar.gz > /tmp/google-cloud-sdk.tar.gz + +# Installing the package +RUN mkdir -p /usr/local/share \ + && tar -C /usr/local/share -xvf /tmp/google-cloud-sdk.tar.gz \ + && /usr/local/share/google-cloud-sdk/install.sh + +# Adding the package path to local +ENV PATH $PATH:/usr/local/gcloud/google-cloud-sdk/bin COPY icons /icons COPY --from=build /src/bin/k8sviz / diff --git a/Makefile b/Makefile index 38f3b4d..497c49b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -IMAGE ?= mkimuram/k8sviz +IMAGE ?= sguerard/k8sviz TAG ?= 0.3 DEVEL_IMAGE ?= k8sviz DEVEL_TAG ?= devel @@ -47,3 +47,13 @@ image-push: image-build docker push $(IMAGE):$(TAG) .PHONY: test test-lint test-fmt test-vet test-unit test-e2e build release image-build image-push + +generate-graph: + mkdir -p out + ./k8sviz.sh -t dot -k config-k8sviz -o out -n "ns1,ns2" -f k8s-diagram.png + +merge-graph: + m4 merge.m4 > merged.gv + sed -i -e "s/\/icons/icons/" merged.gv + dot -n -Tpng merged.gv -o diagram_staging.png + rm merged.gv diff --git a/README.md b/README.md index ac7e59a..28a0900 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,12 @@ $ cp -r icons ${PATH_TO_INSTALL} $ ./k8sviz.sh --help USAGE: ./k8sviz.sh [flags] args flags: - -n,--namespace: The namespace to visualize. (default: 'default') - -o,--outfile: The filename to output. (default: 'k8sviz.out') + -n,--namespace: The namespace to visualize. It is possible to specify many coma separated namespaces (default: 'default') + -o,--outdir: The directory to generate files. filename use namespace name (default: 'out') -t,--type: The type of output. (default: 'dot') -k,--kubeconfig: Path to kubeconfig file. (default: '/home/user1/kubeconfig') -i,--image: Image name of the container. (default: 'mkimuram/k8sviz:0.3') + -f,--filename: concatenated diagram filename (default: "global.dot") -h,--help: show this help (default: false) ``` @@ -91,13 +92,13 @@ Usage of ./k8sviz: -kubeconfig string absolute path to the kubeconfig file (default "/home/user1/.kube/config") -n string - namespace to visualize (shorthand) (default "namespace") + namespace to visualize. It is possible to specify many coma separated namespaces (shorthand) (default "namespace") -namespace string - namespace to visualize (default "namespace") + namespace to visualize. It is possible to specify many coma separated namespaces (default "namespace") -o string - output filename (shorthand) (default "k8sviz.out") - -outfile string - output filename (default "k8sviz.out") + output directory (shorthand) (default "out") + -outdir string + output directory. filename is based on each namespace name (default "k8sviz.out") -t string type of output (shorthand) (default "dot") -type string diff --git a/cmd/k8sviz/main.go b/cmd/k8sviz/main.go index ff28b9b..0475623 100644 --- a/cmd/k8sviz/main.go +++ b/cmd/k8sviz/main.go @@ -6,24 +6,23 @@ package main import ( "flag" "fmt" - "os" - "path/filepath" - "github.com/mkimuram/k8sviz/pkg/graph" "github.com/mkimuram/k8sviz/pkg/resources" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" + "os" + "path/filepath" + "strings" _ "k8s.io/client-go/plugin/pkg/client/auth" ) const ( defaultNamespace = "namespace" - defaultOutFile = "k8sviz.out" + defaultOutDir = "out" defaultOutType = "dot" descNamespaceOpt = "namespace to visualize" - descOutFileOpt = "output filename" + descOutDirOpt = "output dir" descOutTypeOpt = "type of output" descShortOptSuffix = " (shorthand)" ) @@ -33,7 +32,7 @@ var ( dir string // Flags namespace string - outFile string + outDir string outType string ) @@ -49,8 +48,8 @@ func init() { } flag.StringVar(&namespace, "namespace", defaultNamespace, descNamespaceOpt) flag.StringVar(&namespace, "n", defaultNamespace, descNamespaceOpt+descShortOptSuffix) - flag.StringVar(&outFile, "outfile", defaultOutFile, descOutFileOpt) - flag.StringVar(&outFile, "o", defaultOutFile, descOutFileOpt+descShortOptSuffix) + flag.StringVar(&outDir, "outDir", defaultOutDir, descOutDirOpt) + flag.StringVar(&outDir, "o", defaultOutDir, descOutDirOpt+descShortOptSuffix) flag.StringVar(&outType, "type", defaultOutType, descOutTypeOpt) flag.StringVar(&outType, "t", defaultOutType, descOutTypeOpt+descShortOptSuffix) flag.Parse() @@ -70,11 +69,13 @@ func init() { } // test connectivity for k8s cluster and the namespace - _, err = clientset.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{}) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to get namespace %q: %v\n", namespace, err) - os.Exit(1) - } + //if !strings.Contains(namespace,",") { + // _, err = clientset.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{}) + // if err != nil { + // fmt.Fprintf(os.Stderr, "Failed to get namespace %q: %v\n", namespace, err) + // os.Exit(1) + // } + //} dir, err = getBinDir() if err != nil { @@ -85,23 +86,25 @@ func init() { func main() { // Get all resources in the namespace - res, err := resources.NewResources(clientset, namespace) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to get k8s resources: %v\n", err) - os.Exit(1) - } - - g := graph.NewGraph(res, dir) - - if outType == "dot" { - if err := g.WriteDotFile(outFile); err != nil { - fmt.Fprintf(os.Stderr, "Failed to output %q file with format %q for namespace %q: %v\n", outFile, outType, namespace, err) + words := strings.Split(namespace, ",") + for _, ns := range words { + res, err := resources.NewResources(clientset, ns) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get k8s resources: %v\n", err) os.Exit(1) } - } else { - if err := g.PlotDotFile(outFile, outType); err != nil { - fmt.Fprintf(os.Stderr, "Failed to output %q file with format %q for namespace %q: %v\n", outFile, outType, namespace, err) - os.Exit(1) + + g := graph.NewGraph(res, dir) + if outType == "dot" { + if err := g.WriteDotFile(outDir + "/" + ns + "." + outType); err != nil { + fmt.Fprintf(os.Stderr, "Failed to output %q file with format %q for namespace %q: %v\n", outDir, outType, namespace, err) + os.Exit(1) + } + } else { + if err := g.PlotDotFile(outDir+"/"+ns+"."+outType, outType); err != nil { + fmt.Fprintf(os.Stderr, "Failed to output %q file with format %q for namespace %q: %v\n", outDir, outType, namespace, err) + os.Exit(1) + } } } } diff --git a/go.sum b/go.sum index e2aa1ad..842fb73 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,4 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -36,7 +35,6 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -54,7 +52,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -66,7 +63,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= @@ -86,7 +82,6 @@ github.com/gophercloud/gophercloud v0.9.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU80 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= @@ -117,13 +112,11 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.15.2 h1:l77YT15o814C2qVL47NOyjV/6RbaP7kKdrvZnxQ3Org= github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= @@ -146,11 +139,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU= golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -168,9 +159,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk= golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= @@ -193,12 +182,10 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -208,7 +195,6 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -224,7 +210,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -233,7 +218,6 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -252,7 +236,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -262,7 +245,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/icons/cj-128.png b/icons/cj-128.png new file mode 100644 index 0000000..f3f4c42 Binary files /dev/null and b/icons/cj-128.png differ diff --git a/k8sviz.sh b/k8sviz.sh index 71b111a..5c36ee4 100755 --- a/k8sviz.sh +++ b/k8sviz.sh @@ -2,10 +2,12 @@ #### Variables #### NAMESPACE="default" -OUTFILE="k8sviz.out" +OUTDIR="out" TYPE="dot" +GLOBAL_FILENAME="" KUBECONFIG=~/kubeconfig -CONTAINER_IMG=mkimuram/k8sviz:0.3 +#CONTAINER_IMG=mkimuram/k8sviz:0.3 +CONTAINER_IMG=docker.io/library/k8sviz:devel SHFLAGS_DIR="$(dirname ${BASH_SOURCE})/lib/" SHFLAGS_PATH="${SHFLAGS_DIR}shflags" SHFLAGS_URL="https://raw.githubusercontent.com/kward/shflags/master/shflags" @@ -36,20 +38,21 @@ fi . ${SHFLAGS_PATH} DEFINE_string 'namespace' "${NAMESPACE}" 'The namespace to visualize.' 'n' -DEFINE_string 'outfile' "${OUTFILE}" 'The filename to output.' 'o' +DEFINE_string 'outdir' "${OUTDIR}" 'The directory to output.' 'o' DEFINE_string 'type' "${TYPE}" 'The type of output.' 't' DEFINE_string 'kubeconfig' "${KUBECONFIG}" 'Path to kubeconfig file.' 'k' DEFINE_string 'image' "${CONTAINER_IMG}" 'Image name of the container.' 'i' +DEFINE_string 'filename' "${GLOBAL_FILENAME}" 'global filename with concatenated diagram.' 'f' # Parse Options FLAGS "$@" || exit $? eval set -- "${FLAGS_ARGV}" #### Main #### -# Split OUTFILE to the directory and the filename to be used with container -DIR=$(dirname ${FLAGS_outfile}) +# Split OUTDIR to the directory and the filename to be used with container +DIR=$(dirname ${FLAGS_outdir}) ABSDIR=$(cd ${DIR}; pwd -P) -FILENAME=$(basename ${FLAGS_outfile}) +FILENAME=$(basename ${FLAGS_outdir}) # Make KUBECONFIG to absolute path KUBEDIR=$(dirname ${FLAGS_kubeconfig}) @@ -64,10 +67,31 @@ if [ ! -f "${KUBECONFIG}" ];then exit 1 fi -docker run --network host \ - --user $(id -u):$(id -g) \ - -v ${ABSDIR}:/work \ - -v ${KUBECONFIG}:/config:ro \ - -it --rm ${FLAGS_image} \ - /k8sviz -kubeconfig /config \ - -n ${FLAGS_namespace} -t ${FLAGS_type} -o /work/${FILENAME} +if [ -z $GLOBAL_FILENAME ]; then + docker run --network host \ + --user $(id -u):$(id -g) \ + -v ${ABSDIR}:/work \ + -v ${KUBECONFIG}:/config:ro \ + -e GOOGLE_APPLICATION_CREDENTIALS=/work/service-account-key.json \ + -it --rm ${FLAGS_image} \ + /k8sviz -kubeconfig /config \ + -t ${FLAGS_type} -n ${FLAGS_namespace} -o /work/${FILENAME} + +else + docker run --network host \ + --user $(id -u):$(id -g) \ + -v ${ABSDIR}:/work \ + -v ${KUBECONFIG}:/config:ro \ + -e GOOGLE_APPLICATION_CREDENTIALS=/work/service-account-key.json \ + -it --rm ${FLAGS_image} \ + /k8sviz -kubeconfig /config \ + -n ${FLAGS_namespace} -t 'dot' -o /work/${FILENAME} + m4 merge.m4 > merged.gv + sed -i -e "s/\/icons/icons/" merged.gv + if [ "dot" != "${FLAGS_type}" ]; then + dot -n -T${FLAGS_type} merged.gv -o $GLOBAL_FILENAME.${FLAGS_type} + rm merged.gv + else + mv merged.gv $GLOBAL_FILENAME.${FLAGS_type} + fi +fi diff --git a/merge.m4 b/merge.m4 new file mode 100644 index 0000000..7db6c99 --- /dev/null +++ b/merge.m4 @@ -0,0 +1,6 @@ +### merge dot files +digraph k8s_diagram { +define(`digraph',`subgraph') +include(out/ns1.dot)dnl +include(out/ns2.dot)dnl +} diff --git a/pkg/graph/graph.go b/pkg/graph/graph.go index 42a07e0..fb98115 100644 --- a/pkg/graph/graph.go +++ b/pkg/graph/graph.go @@ -6,13 +6,12 @@ package graph import ( "bytes" "fmt" - "os" - "os/exec" - "strings" - "github.com/awalterschulze/gographviz" "github.com/mkimuram/k8sviz/pkg/resources" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "os" + "os/exec" + "strings" ) // Graph represents a graph of k8s resources @@ -36,7 +35,6 @@ func (g *Graph) WriteDotFile(outFile string) error { if err != nil { return err } - if _, err := f.WriteString(g.toDot()); err != nil { if closeErr := f.Close(); closeErr != nil { return fmt.Errorf("failed to close file after write failure: %v, %v", closeErr, err) @@ -125,15 +123,15 @@ func (g *Graph) generateCommon() { if err != nil { fmt.Fprintf(os.Stderr, "Failed to set to digraph: %v\n", err) } - err = g.gviz.SetName("G") + err = g.gviz.SetName("G_" + g.escapeName(g.res.Namespace)) if err != nil { fmt.Fprintf(os.Stderr, "Failed to set to graph name to G: %v\n", err) } - err = g.gviz.AddAttr("G", "rankdir", "TD") + err = g.gviz.AddAttr("G_"+g.escapeName(g.res.Namespace), "rankdir", "TD") if err != nil { fmt.Fprintf(os.Stderr, "Failed to set rankdir to TD: %v\n", err) } - err = g.gviz.AddSubGraph("G", g.clusterName(), + err = g.gviz.AddSubGraph("G_"+g.escapeName(g.res.Namespace), g.clusterName(), map[string]string{"label": g.clusterLabel(), "labeljust": "l", "style": "dotted"}) if err != nil { fmt.Fprintf(os.Stderr, "Failed to add subgraph %s to digraph G: %v\n", g.clusterName(), err) @@ -217,6 +215,9 @@ func (g *Graph) generateEdges() { // Owner reference for rs g.genRsOwnerRef() + // Owner reference for job + g.genJobOwnerRef() + // hpa to scale target g.genHpaScaleTargetRef() @@ -260,6 +261,21 @@ func (g *Graph) genRsOwnerRef() { } } +// genJobOwnerRef generates the edges of OwnerReferences from RS +func (g *Graph) genJobOwnerRef() { + // Add edge if below matches: + // - apps/v1.ReplicaSet.metadata.ownerReferences. + // - kind + // - name + // - {kind}.metadata.{name} + // ``` + // deploy_my_deployment->rs_my_replicaset[ style=dashed ]; + // ``` + for _, job := range g.res.Jobs.Items { + g.genOwnerRef("job", &job) + } +} + // genOwnerRef generates the edges of OwnerReferences for specified obj func (g *Graph) genOwnerRef(kind string, obj metav1.Object) { for _, ref := range obj.GetOwnerReferences() { diff --git a/pkg/graph/testdata/common.golden b/pkg/graph/testdata/common.golden index 52109d3..5c80e71 100644 --- a/pkg/graph/testdata/common.golden +++ b/pkg/graph/testdata/common.golden @@ -1,4 +1,4 @@ -digraph G { +digraph G_testns { rankdir=TD; 0->1[ style=invis ]; 1->2[ style=invis ]; diff --git a/pkg/graph/testdata/generate_res1.golden b/pkg/graph/testdata/generate_res1.golden index bc1fed5..a0fd989 100644 --- a/pkg/graph/testdata/generate_res1.golden +++ b/pkg/graph/testdata/generate_res1.golden @@ -1,4 +1,4 @@ -digraph G { +digraph G_testns { rankdir=TD; 0->1[ style=invis ]; 1->2[ style=invis ]; diff --git a/pkg/graph/testdata/generate_res2.golden b/pkg/graph/testdata/generate_res2.golden index e28c52c..49db7e8 100644 --- a/pkg/graph/testdata/generate_res2.golden +++ b/pkg/graph/testdata/generate_res2.golden @@ -1,4 +1,4 @@ -digraph G { +digraph G_testns { rankdir=TD; 0->1[ style=invis ]; 1->2[ style=invis ]; diff --git a/pkg/graph/testdata/generate_res3.golden b/pkg/graph/testdata/generate_res3.golden index 800b720..625ab3e 100644 --- a/pkg/graph/testdata/generate_res3.golden +++ b/pkg/graph/testdata/generate_res3.golden @@ -1,4 +1,4 @@ -digraph G { +digraph G_testns { rankdir=TD; 0->1[ style=invis ]; 1->2[ style=invis ]; diff --git a/pkg/resources/resources.go b/pkg/resources/resources.go index 85b811c..5d72afc 100644 --- a/pkg/resources/resources.go +++ b/pkg/resources/resources.go @@ -10,6 +10,7 @@ import ( appsv1 "k8s.io/api/apps/v1" autov1 "k8s.io/api/autoscaling/v1" batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/v1" v1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -19,7 +20,7 @@ import ( var ( // ResourceTypes represents the set of resource types. // Resouces are grouped by the same level of abstraction. - ResourceTypes = []string{"hpa", "deploy job", "sts ds rs", "pod", "pvc", "svc", "ing"} + ResourceTypes = []string{"hpa cj", "deploy job", "sts ds rs", "pod", "pvc", "svc", "ing"} normalizedNames = map[string]string{ "ns": "namespace", "svc": "service", @@ -30,6 +31,7 @@ var ( "rs": "replicaset", "deploy": "deployment", "job": "job", + "cj": "cronjob", "ing": "ingress", "hpa": "horizontalpodautoscaler", } @@ -48,6 +50,7 @@ type Resources struct { Rss *appsv1.ReplicaSetList Deploys *appsv1.DeploymentList Jobs *batchv1.JobList + CronJobs *batchv1beta1.CronJobList Ingresses *v1beta1.IngressList Hpas *autov1.HorizontalPodAutoscalerList } @@ -99,6 +102,12 @@ func NewResources(clientset kubernetes.Interface, namespace string) (*Resources, return nil, fmt.Errorf("failed to get deployments in namespace %q: %v", namespace, err) } + // CronJobs + res.CronJobs, err = clientset.BatchV1beta1().CronJobs(namespace).List(metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to get cronjobs in namespace %q: %v", namespace, err) + } + // job res.Jobs, err = clientset.BatchV1().Jobs(namespace).List(metav1.ListOptions{}) if err != nil { @@ -153,6 +162,10 @@ func (r *Resources) GetResourceNames(kind string) []string { for _, n := range r.Deploys.Items { names = append(names, n.Name) } + case "cj": + for _, n := range r.CronJobs.Items { + names = append(names, n.Name) + } case "job": for _, n := range r.Jobs.Items { names = append(names, n.Name)