Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integrity information to must-gather #1848

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/cmd/operator/mustgather.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package operator

import (
"context"
"encoding/hex"
"fmt"
"time"

Expand Down Expand Up @@ -320,5 +321,11 @@ func (o *MustGatherOptions) run(ctx context.Context) error {
}
}

integritySHA, err := collector.WriteIntegrityFile()
if err != nil {
errs = append(errs, fmt.Errorf("can't write integrity file: %w", err))
}
klog.InfoS("Witten integrity file", "SHA", hex.EncodeToString(integritySHA))

return utilerrors.NewAggregate(errs)
}
43 changes: 43 additions & 0 deletions pkg/cmd/operator/mustgather_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/google/go-cmp/cmp"
scyllav1 "github.com/scylladb/scylla-operator/pkg/api/scylla/v1"
scyllav1alpha1 "github.com/scylladb/scylla-operator/pkg/api/scylla/v1alpha1"
"github.com/scylladb/scylla-operator/pkg/gather/collect"
"github.com/scylladb/scylla-operator/pkg/gather/collect/testhelpers"
"github.com/scylladb/scylla-operator/pkg/genericclioptions"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -147,6 +148,25 @@ func TestMustGatherOptions_Run(t *testing.T) {
expectedDump: &testhelpers.GatherDump{
EmptyDirs: nil,
Files: []testhelpers.File{
{
Name: collect.IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: f3d8c4e7e30f0170945cd374ca6a2e6dbfaa84a1534e5b3da4f5bbfab165369c9c89f982779cb2630dfc9392876edf4daf5906d285741dfcf3d6f65bf688db66
directories:
/cluster-scoped/namespaces:
checksum: bbaf277edfa96fc011b6ca9db1b76eae803d26ab6169f816e88d223e2808e0dadc1733e71a8c0288630c7ab0ec77d1be34b3d78daa62f2f2b90c0dfabc3b2331
fileCount: 1
/namespaces/my-namespace/scyllaclusters.scylla.scylladb.com:
checksum: 7ab5ffe5b82d7f78398b1a4f7081ac649c28eeef72bf89cdd70eaba7f266f2d109415e7186555a718b784239677c53ddb97be08a20303fa36580c8e73c73dfff
fileCount: 1
/namespaces/my-other-namespace/scyllaclusters.scylla.scylladb.com:
checksum: f06efc479e7c5363282891bfebf7906d388bb1f6e8aa7852510b8025ee00f89bf30d648402230f310ed1e91f7266ad7434a9e56f142cdb7c9eb25eb69b9340b7
fileCount: 1
/namespaces/scylla-operator/secrets:
checksum: e46f1567de5a4a6d647595ee5f5ca424f6c05e6c044b71f694735379577250629ed421fc9d71c8e048067a3b04980e126d832589ada664609ebd5a81597920e6
fileCount: 1
`, "\n"),
},
{
Name: "cluster-scoped/namespaces/scylla-operator.yaml",
Content: strings.TrimPrefix(`
Expand Down Expand Up @@ -248,6 +268,19 @@ metadata:
expectedDump: &testhelpers.GatherDump{
EmptyDirs: nil,
Files: []testhelpers.File{
{
Name: collect.IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: 6ad59d9fa641a55f9f81b554d0ea73a19d3fa03c05e08aceb757076158aab691efae7c6ca040f44e142764d71fbe25b67265a33ddb10e32d91016ce69ba99605
directories:
/cluster-scoped/namespaces:
checksum: bbaf277edfa96fc011b6ca9db1b76eae803d26ab6169f816e88d223e2808e0dadc1733e71a8c0288630c7ab0ec77d1be34b3d78daa62f2f2b90c0dfabc3b2331
fileCount: 1
/namespaces/my-namespace/scyllaclusters.scylla.scylladb.com:
checksum: 7ab5ffe5b82d7f78398b1a4f7081ac649c28eeef72bf89cdd70eaba7f266f2d109415e7186555a718b784239677c53ddb97be08a20303fa36580c8e73c73dfff
fileCount: 1
`, "\n"),
},
{
Name: "cluster-scoped/namespaces/scylla-operator.yaml",
Content: strings.TrimPrefix(`
Expand Down Expand Up @@ -300,6 +333,16 @@ status: {}
expectedDump: &testhelpers.GatherDump{
EmptyDirs: nil,
Files: []testhelpers.File{
{
Name: collect.IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: 3198c682f6798c42c7b94228c7f3568f045abcb8ad191caa67fc568b5dd93afd48c2fb3da372c3ebe4cf5c51a7711ddcffe8a488b4da6e1702dd2622b59c8323
directories:
/namespaces/storageversions.internal.apiserver.k8s.io:
checksum: 3198c682f6798c42c7b94228c7f3568f045abcb8ad191caa67fc568b5dd93afd48c2fb3da372c3ebe4cf5c51a7711ddcffe8a488b4da6e1702dd2622b59c8323
fileCount: 1
`, "\n"),
},
{
Name: "namespaces/storageversions.internal.apiserver.k8s.io/my-non-standard-resource.yaml",
Content: strings.TrimPrefix(`
Expand Down
56 changes: 38 additions & 18 deletions pkg/gather/collect/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package collect
import (
"bytes"
"context"
"crypto/sha512"
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/scylladb/scylla-operator/pkg/helpers"
"github.com/scylladb/scylla-operator/pkg/helpers/slices"
Expand All @@ -26,11 +28,13 @@ import (
"k8s.io/client-go/dynamic"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/klog/v2"
"sigs.k8s.io/yaml"
)

const (
namespacesDirName = "namespaces"
clusterScopedDirName = "cluster-scoped"
IntegrityFileName = ".integrity.yaml"
)

type ResourceInfo struct {
Expand All @@ -45,23 +49,6 @@ func NewResourceInfoFromMapping(mapping *meta.RESTMapping) *ResourceInfo {
}
}

func writeObject(printer ResourcePrinterInterface, filePath string, resourceInfo *ResourceInfo, obj kubeinterfaces.ObjectInterface) error {
buf := bytes.NewBuffer(nil)
err := printer.PrintObj(resourceInfo, obj, buf)
if err != nil {
return fmt.Errorf("can't print object %q (%s): %w", naming.ObjRef(obj), resourceInfo.Resource, err)
}

err = os.WriteFile(filePath, buf.Bytes(), 0770)
if err != nil {
return fmt.Errorf("can't write file %q: %w", filePath, err)
}

klog.V(4).InfoS("Written resource", "Path", filePath)

return nil
}

func getResourceKey(obj *unstructured.Unstructured, resourceInfo *ResourceInfo) string {
var nsPrefix string
if resourceInfo.Scope.Name() == meta.RESTScopeNameNamespace {
Expand All @@ -82,6 +69,7 @@ type Collector struct {
logsLimitBytes int64

collectedResources sets.Set[string]
counters *Counters
}

func NewCollector(
Expand All @@ -104,6 +92,7 @@ func NewCollector(
keepGoing: keepGoing,
logsLimitBytes: logsLimitBytes,
collectedResources: sets.Set[string]{},
counters: NewCounters(),
}
}

Expand Down Expand Up @@ -134,10 +123,24 @@ func (c *Collector) writeObject(ctx context.Context, dirPath string, obj kubeint
var err error
for _, printer := range c.printers {
filePath := filepath.Join(dirPath, obj.GetName()+printer.GetSuffix())
err = writeObject(printer, filePath, resourceInfo, obj)
buf := bytes.NewBuffer(nil)

err = printer.PrintObj(resourceInfo, obj, buf)
if err != nil {
return fmt.Errorf("can't print object %q (%s): %w", naming.ObjRef(obj), resourceInfo.Resource, err)
}

err = os.WriteFile(filePath, buf.Bytes(), 0770)
if err != nil {
return fmt.Errorf("can't write file %q: %w", filePath, err)
}

err = c.counters.writeFile(strings.TrimPrefix(dirPath, c.baseDir), buf.Bytes())
if err != nil {
return fmt.Errorf("can't write object: %w", err)
}

klog.V(4).InfoS("Written resource", "Path", filePath)
}

return nil
Expand Down Expand Up @@ -504,3 +507,20 @@ func (c *Collector) CollectResources(ctx context.Context, resourceInfo *Resource

return apierrors.NewAggregate(errs)
}

func (c *Collector) WriteIntegrityFile() ([]byte, error) {
data, err := yaml.Marshal(c.counters)
if err != nil {
return nil, fmt.Errorf("can't marsah identity: %w", err)
}

integrityFilePath := filepath.Join(c.baseDir, IntegrityFileName)
err = os.WriteFile(integrityFilePath, data, 0770)
if err != nil {
return nil, fmt.Errorf("can't write file %q: %w", integrityFilePath, err)
}

h := sha512.New()
h.Write(data)
return h.Sum(nil), nil
}
68 changes: 68 additions & 0 deletions pkg/gather/collect/collect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ func TestCollector_CollectObject(t *testing.T) {
"namespaces/test/pods/my-pod",
},
Files: []testhelpers.File{
{
Name: IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: d763fa8cc6665af15380368055bb31a5720ef80d7e49232a4527e450d3d8a5fa129cac17068863ce63374afb32494089462ad79e47898b48a3f24957334fd4e8
directories:
/namespaces/test/pods:
checksum: d763fa8cc6665af15380368055bb31a5720ef80d7e49232a4527e450d3d8a5fa129cac17068863ce63374afb32494089462ad79e47898b48a3f24957334fd4e8
fileCount: 1
`, "\n"),
},
{
Name: "namespaces/test/pods/my-pod.yaml",
Content: strings.TrimPrefix(`
Expand Down Expand Up @@ -125,6 +135,16 @@ status: {}
"namespaces/test/pods/my-pod",
},
Files: []testhelpers.File{
{
Name: IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: d2503c86cb530b941dd9239ee48033d804b5e7d1e4861b0b116d205b38232c9564ed13634b96901f1e471777b419754962bbe559921a34b0bb0b54b8e11fe303
directories:
/namespaces/test/pods:
checksum: d2503c86cb530b941dd9239ee48033d804b5e7d1e4861b0b116d205b38232c9564ed13634b96901f1e471777b419754962bbe559921a34b0bb0b54b8e11fe303
fileCount: 1
`, "\n"),
},
{
Name: "namespaces/test/pods/my-pod.yaml",
Content: strings.TrimPrefix(`
Expand Down Expand Up @@ -215,6 +235,16 @@ status:
expectedDump: &testhelpers.GatherDump{
EmptyDirs: nil,
Files: []testhelpers.File{
{
Name: IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: 25149bc2e1d1c698989b4f6c9d6a9d87bd51369bc9328b2923ab1cddda62ecc397f254634a184da550a3b39d114d7c19a69cfd7cb266576907656ba1e6535bd7
directories:
/namespaces/test/pods:
checksum: 25149bc2e1d1c698989b4f6c9d6a9d87bd51369bc9328b2923ab1cddda62ecc397f254634a184da550a3b39d114d7c19a69cfd7cb266576907656ba1e6535bd7
fileCount: 1
`, "\n"),
},
{
Name: "namespaces/test/pods/my-pod.yaml",
Content: strings.TrimPrefix(`
Expand Down Expand Up @@ -351,6 +381,16 @@ status:
expectedDump: &testhelpers.GatherDump{
EmptyDirs: nil,
Files: []testhelpers.File{
{
Name: IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: 35795801b54e77cfbe1567452e2ab7053631a02696dcc7418e173c6db99fb4dc0fab98f444b6020be77da398e8b147a47b08c64a3bfb8e96912f4aa67c04e677
directories:
/namespaces/test/pods:
checksum: 35795801b54e77cfbe1567452e2ab7053631a02696dcc7418e173c6db99fb4dc0fab98f444b6020be77da398e8b147a47b08c64a3bfb8e96912f4aa67c04e677
fileCount: 1
`, "\n"),
},
{
Name: "namespaces/test/pods/my-pod.yaml",
Content: strings.TrimPrefix(`
Expand Down Expand Up @@ -457,6 +497,16 @@ status:
expectedDump: &testhelpers.GatherDump{
EmptyDirs: nil,
Files: []testhelpers.File{
{
Name: IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: 3b11d89fb3b46dd29852f0efd5eefbced9d664f0d7b93aefa8532d62c856c6a6ba0cc7eb1f898d1c337d6827d77a4d325e6ae6c555f0bf0b0973819bf85a1c51
directories:
/cluster-scoped/namespaces:
checksum: 3b11d89fb3b46dd29852f0efd5eefbced9d664f0d7b93aefa8532d62c856c6a6ba0cc7eb1f898d1c337d6827d77a4d325e6ae6c555f0bf0b0973819bf85a1c51
fileCount: 1
`, "\n"),
},
{
Name: "cluster-scoped/namespaces/my-namespace.yaml",
Content: strings.TrimPrefix(`
Expand Down Expand Up @@ -497,6 +547,19 @@ status: {}
expectedDump: &testhelpers.GatherDump{
EmptyDirs: nil,
Files: []testhelpers.File{
{
Name: IntegrityFileName,
Content: strings.TrimPrefix(`
checksum: 7f340ac7a111169126248f69c955b347d6ff5422e0cc792031bd8398dc766137896fcc188aa3ccbc274a6128296572e971dde235fc050cbf1baaa1af3a6b397a
directories:
/cluster-scoped/namespaces:
checksum: 03cb220813bf01d43c5c8316e4a3b5a766770fa3437196ddaabc478269025570f33c93f6c54791af2b450e3b197af0df5ca8ec4c3285b427c743c40b771fc225
fileCount: 1
/namespaces/my-namespace/secrets:
checksum: 99a521ab8ba30d21687b0eca6ffa1f4a4a98c2ca05a016d5f215b9a8690820abbbfddf63be1e486392a236c2c91b2a19e0ec50644c97ecb6d4b39406ecaa2e48
fileCount: 1
`, "\n"),
},
{
Name: "cluster-scoped/namespaces/my-namespace.yaml",
Content: strings.TrimPrefix(`
Expand Down Expand Up @@ -600,6 +663,11 @@ metadata:
t.Fatal(err)
}

_, err = collector.WriteIntegrityFile()
if err != nil {
t.Fatal(err)
}

got, err := testhelpers.ReadGatherDump(tmpDir)
if err != nil {
t.Fatal(err)
Expand Down
69 changes: 69 additions & 0 deletions pkg/gather/collect/counter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package collect

import (
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
)

type hasher struct {
hash.Hash
}

func (h hasher) MarshalJSON() ([]byte, error) {
return json.Marshal(hex.EncodeToString(h.Sum(nil)))
}

type IntegrityCounter struct {
FileCount uint `json:"fileCount"`
Hasher hasher `json:"checksum"`
}

func (ic *IntegrityCounter) writeFile(data []byte) error {
if ic.FileCount == 0 {
ic.Hasher = hasher{sha512.New()}
}
ic.FileCount++

_, err := ic.Hasher.Write(data)
if err != nil {
return fmt.Errorf("can't write data to hasher: %w", err)
}

return nil
}

type Counters struct {
Dirs map[string]IntegrityCounter `json:"directories"`
Hasher hasher `json:"checksum"`
}

func NewCounters() *Counters {
return &Counters{
Dirs: map[string]IntegrityCounter{},
Hasher: hasher{sha512.New()},
}
}

func (c *Counters) writeFile(dirPath string, data []byte) error {
_, err := c.Hasher.Write(data)
if err != nil {
return fmt.Errorf("can't hash data: %w", err)
}

ic, found := c.Dirs[dirPath]
if !found {
ic = IntegrityCounter{}
}

err = ic.writeFile(data)
if err != nil {
return fmt.Errorf("can't write file to identity counter: %w", err)
}

c.Dirs[dirPath] = ic

return nil
}