mirror of https://github.com/fluxcd/flux2.git
commit
f887a2c029
@ -0,0 +1,89 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{ .fluxns }}
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: flux-system
|
||||
namespace: {{ .fluxns }}
|
||||
spec:
|
||||
path: ./clusters/production
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
interval: 5m
|
||||
prune: true
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2021-08-01T04:52:56Z"
|
||||
message: 'Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
|
||||
reason: ReconciliationSucceeded
|
||||
status: "True"
|
||||
type: Ready
|
||||
inventory:
|
||||
entries:
|
||||
- id: _{{ .fluxns }}__Namespace
|
||||
v: v1
|
||||
- id: {{ .fluxns }}_helm-controller_apps_Deployment
|
||||
v: v1
|
||||
- id: {{ .fluxns }}_kustomize-controller_apps_Deployment
|
||||
v: v1
|
||||
- id: {{ .fluxns }}_notification-controller_apps_Deployment
|
||||
v: v1
|
||||
- id: {{ .fluxns }}_source-controller_apps_Deployment
|
||||
v: v1
|
||||
- id: {{ .fluxns }}_infrastructure_kustomize.toolkit.fluxcd.io_Kustomization
|
||||
v: v1beta2
|
||||
- id: {{ .fluxns }}_flux-system_source.toolkit.fluxcd.io_GitRepository
|
||||
v: v1beta1
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: infrastructure
|
||||
namespace: {{ .fluxns }}
|
||||
spec:
|
||||
path: ./infrastructure/production
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
interval: 5m
|
||||
prune: true
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2021-08-01T04:52:56Z"
|
||||
message: 'Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
|
||||
reason: ReconciliationSucceeded
|
||||
status: "True"
|
||||
type: Ready
|
||||
inventory:
|
||||
entries:
|
||||
- id: _cert-manager__Namespace
|
||||
v: v1
|
||||
- id: cert-manager_cert-manager_source.toolkit.fluxcd.io_HelmRepository
|
||||
v: v1beta1
|
||||
- id: cert-manager_cert-manager_helm.toolkit.fluxcd.io_HelmRelease
|
||||
v: v2beta1
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: empty
|
||||
namespace: {{ .fluxns }}
|
||||
spec:
|
||||
path: ./apps/todo
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
interval: 5m
|
||||
prune: true
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2021-08-01T04:52:56Z"
|
||||
message: 'Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
|
||||
reason: ReconciliationSucceeded
|
||||
status: "True"
|
||||
type: Ready
|
@ -0,0 +1,6 @@
|
||||
Kustomization/{{ .fluxns }}/flux-system
|
||||
├── Kustomization/{{ .fluxns }}/infrastructure
|
||||
│ ├── HelmRepository/cert-manager/cert-manager
|
||||
│ └── HelmRelease/cert-manager/cert-manager
|
||||
└── GitRepository/{{ .fluxns }}/flux-system
|
||||
|
@ -0,0 +1,2 @@
|
||||
Kustomization/{{ .fluxns }}/empty
|
||||
|
@ -0,0 +1,12 @@
|
||||
Kustomization/{{ .fluxns }}/flux-system
|
||||
├── Namespace/{{ .fluxns }}
|
||||
├── Deployment/{{ .fluxns }}/helm-controller
|
||||
├── Deployment/{{ .fluxns }}/kustomize-controller
|
||||
├── Deployment/{{ .fluxns }}/notification-controller
|
||||
├── Deployment/{{ .fluxns }}/source-controller
|
||||
├── Kustomization/{{ .fluxns }}/infrastructure
|
||||
│ ├── Namespace/cert-manager
|
||||
│ ├── HelmRepository/cert-manager/cert-manager
|
||||
│ └── HelmRelease/cert-manager/cert-manager
|
||||
└── GitRepository/{{ .fluxns }}/flux-system
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2021 The Flux 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 main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var treeCmd = &cobra.Command{
|
||||
Use: "tree",
|
||||
Short: "Print the resources reconciled by Flux",
|
||||
Long: `The tree command shows the list of resources reconciled by a Flux object.'`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(treeCmd)
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
/*
|
||||
Copyright 2021 The Flux 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/tree"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var treeKsCmd = &cobra.Command{
|
||||
Use: "kustomization [name]",
|
||||
Aliases: []string{"ks", "kustomization"},
|
||||
Short: "Print the resource inventory of a Kustomization",
|
||||
Long: `The tree command prints the resource list reconciled by a Kustomization.'`,
|
||||
Example: ` # Print the resources managed by the root Kustomization
|
||||
flux tree kustomization flux-system
|
||||
|
||||
# Print the Flux resources managed by the root Kustomization
|
||||
flux tree kustomization flux-system --compact`,
|
||||
RunE: treeKsCmdRun,
|
||||
}
|
||||
|
||||
type TreeKsFlags struct {
|
||||
compact bool
|
||||
output string
|
||||
}
|
||||
|
||||
var treeKsArgs TreeKsFlags
|
||||
|
||||
func init() {
|
||||
treeKsCmd.Flags().BoolVar(&treeKsArgs.compact, "compact", false, "list Flux resources only.")
|
||||
treeKsCmd.Flags().StringVarP(&treeKsArgs.output, "output", "o", "",
|
||||
"the format in which the tree should be printed. can be 'json' or 'yaml'")
|
||||
treeCmd.AddCommand(treeKsCmd)
|
||||
}
|
||||
|
||||
func treeKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("kustomization name is required")
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k := &kustomizev1.Kustomization{}
|
||||
err = kubeClient.Get(ctx, client.ObjectKey{
|
||||
Namespace: rootArgs.namespace,
|
||||
Name: name,
|
||||
}, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kMeta, err := object.CreateObjMetadata(k.Namespace, k.Name,
|
||||
schema.GroupKind{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kTree := tree.New(kMeta)
|
||||
err = treeKustomization(ctx, kTree, k, kubeClient, treeKsArgs.compact)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch treeKsArgs.output {
|
||||
case "json":
|
||||
data, err := json.MarshalIndent(kTree, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootCmd.Println(string(data))
|
||||
case "yaml":
|
||||
data, err := yaml.Marshal(kTree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootCmd.Println(string(data))
|
||||
default:
|
||||
rootCmd.Println(kTree.Print())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func treeKustomization(ctx context.Context, tree tree.ObjMetadataTree, item *kustomizev1.Kustomization, kubeClient client.Client, compact bool) error {
|
||||
if item.Status.Inventory == nil || len(item.Status.Inventory.Entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, entry := range item.Status.Inventory.Entries {
|
||||
objMetadata, err := object.ParseObjMetadata(entry.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if compact && !strings.Contains(objMetadata.GroupKind.Group, "toolkit.fluxcd.io") {
|
||||
continue
|
||||
}
|
||||
|
||||
if objMetadata.GroupKind.Group == kustomizev1.GroupVersion.Group &&
|
||||
objMetadata.GroupKind.Kind == kustomizev1.KustomizationKind &&
|
||||
objMetadata.Namespace == item.Namespace &&
|
||||
objMetadata.Name == item.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
ks := tree.Add(objMetadata)
|
||||
if objMetadata.GroupKind.Group == kustomizev1.GroupVersion.Group &&
|
||||
objMetadata.GroupKind.Kind == kustomizev1.KustomizationKind {
|
||||
k := &kustomizev1.Kustomization{}
|
||||
err = kubeClient.Get(ctx, client.ObjectKey{
|
||||
Namespace: objMetadata.Namespace,
|
||||
Name: objMetadata.Name,
|
||||
}, k)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find object: %w", err)
|
||||
}
|
||||
err := treeKustomization(ctx, ks, k, kubeClient, compact)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
// +build unit
|
||||
|
||||
/*
|
||||
Copyright 2021 The Flux 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 main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTree(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
args string
|
||||
objectFile string
|
||||
goldenFile string
|
||||
}{
|
||||
{
|
||||
"tree kustomization",
|
||||
"tree kustomization flux-system",
|
||||
"testdata/tree/kustomizations.yaml",
|
||||
"testdata/tree/tree.golden",
|
||||
},
|
||||
{
|
||||
"tree kustomization compact",
|
||||
"tree kustomization flux-system --compact",
|
||||
"testdata/tree/kustomizations.yaml",
|
||||
"testdata/tree/tree-compact.golden",
|
||||
},
|
||||
{
|
||||
"tree kustomization empty",
|
||||
"tree kustomization empty",
|
||||
"testdata/tree/kustomizations.yaml",
|
||||
"testdata/tree/tree-empty.golden",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tmpl := map[string]string{
|
||||
"fluxns": allocateNamespace("flux-system"),
|
||||
}
|
||||
testEnv.CreateObjectFile(tc.objectFile, tmpl, t)
|
||||
cmd := cmdTestCase{
|
||||
args: tc.args + " -n=" + tmpl["fluxns"],
|
||||
assert: assertGoldenTemplateFile(tc.goldenFile, tmpl),
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
Copyright 2021 The Flux 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.
|
||||
|
||||
Derived work from https://github.com/d6o/GoTree
|
||||
Copyright (c) 2017 Diego Siqueira
|
||||
*/
|
||||
|
||||
package tree
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fluxcd/pkg/ssa"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
const (
|
||||
newLine = "\n"
|
||||
emptySpace = " "
|
||||
middleItem = "├── "
|
||||
continueItem = "│ "
|
||||
lastItem = "└── "
|
||||
)
|
||||
|
||||
type (
|
||||
objMetadataTree struct {
|
||||
Resource object.ObjMetadata `json:"resource"`
|
||||
ResourceTree []ObjMetadataTree `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
ObjMetadataTree interface {
|
||||
Add(objMetadata object.ObjMetadata) ObjMetadataTree
|
||||
AddTree(tree ObjMetadataTree)
|
||||
Items() []ObjMetadataTree
|
||||
Text() string
|
||||
Print() string
|
||||
}
|
||||
|
||||
printer struct {
|
||||
}
|
||||
|
||||
Printer interface {
|
||||
Print(ObjMetadataTree) string
|
||||
}
|
||||
)
|
||||
|
||||
func New(objMetadata object.ObjMetadata) ObjMetadataTree {
|
||||
return &objMetadataTree{
|
||||
Resource: objMetadata,
|
||||
ResourceTree: []ObjMetadataTree{},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *objMetadataTree) Add(objMetadata object.ObjMetadata) ObjMetadataTree {
|
||||
n := New(objMetadata)
|
||||
t.ResourceTree = append(t.ResourceTree, n)
|
||||
return n
|
||||
}
|
||||
|
||||
func (t *objMetadataTree) AddTree(tree ObjMetadataTree) {
|
||||
t.ResourceTree = append(t.ResourceTree, tree)
|
||||
}
|
||||
|
||||
func (t *objMetadataTree) Text() string {
|
||||
return ssa.FmtObjMetadata(t.Resource)
|
||||
}
|
||||
|
||||
func (t *objMetadataTree) Items() []ObjMetadataTree {
|
||||
return t.ResourceTree
|
||||
}
|
||||
|
||||
func (t *objMetadataTree) Print() string {
|
||||
return newPrinter().Print(t)
|
||||
}
|
||||
|
||||
func newPrinter() Printer {
|
||||
return &printer{}
|
||||
}
|
||||
|
||||
func (p *printer) Print(t ObjMetadataTree) string {
|
||||
return t.Text() + newLine + p.printItems(t.Items(), []bool{})
|
||||
}
|
||||
|
||||
func (p *printer) printText(text string, spaces []bool, last bool) string {
|
||||
var result string
|
||||
for _, space := range spaces {
|
||||
if space {
|
||||
result += emptySpace
|
||||
} else {
|
||||
result += continueItem
|
||||
}
|
||||
}
|
||||
|
||||
indicator := middleItem
|
||||
if last {
|
||||
indicator = lastItem
|
||||
}
|
||||
|
||||
var out string
|
||||
lines := strings.Split(text, "\n")
|
||||
for i := range lines {
|
||||
text := lines[i]
|
||||
if i == 0 {
|
||||
out += result + indicator + text + newLine
|
||||
continue
|
||||
}
|
||||
if last {
|
||||
indicator = emptySpace
|
||||
} else {
|
||||
indicator = continueItem
|
||||
}
|
||||
out += result + indicator + text + newLine
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *printer) printItems(t []ObjMetadataTree, spaces []bool) string {
|
||||
var result string
|
||||
for i, f := range t {
|
||||
last := i == len(t)-1
|
||||
result += p.printText(f.Text(), spaces, last)
|
||||
if len(f.Items()) > 0 {
|
||||
spacesChild := append(spaces, last)
|
||||
result += p.printItems(f.Items(), spacesChild)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
Loading…
Reference in New Issue