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