From c3ce89ab221db9a76e3f68c24b3c7cc8d9ffae4b Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Fri, 20 Oct 2023 20:52:26 +0000 Subject: [PATCH 1/6] write instance and preflight reports to a secret --- pkg/handlers/preflight.go | 2 +- pkg/k8sutil/kotsadm.go | 10 + pkg/k8sutil/kotsadm_test.go | 48 ++++ pkg/reporting/app.go | 9 +- pkg/reporting/app_airgap.go | 133 ++++++++++- pkg/reporting/app_airgap_test.go | 217 +++++++++++++++++ pkg/reporting/preflight.go | 2 +- pkg/reporting/preflight_airgap.go | 102 +++++++- pkg/reporting/preflight_airgap_test.go | 202 ++++++++++++++++ pkg/reporting/types.go | 54 +++++ pkg/reporting/util.go | 66 ++++++ pkg/reporting/util_test.go | 82 +++++++ pkg/store/kotsstore/reporting_store.go | 246 ------------------- pkg/store/mock/mock.go | 316 +++++++++---------------- pkg/store/store_interface.go | 7 - pkg/supportbundle/spec.go | 22 ++ pkg/util/compress.go | 42 ++++ 17 files changed, 1092 insertions(+), 468 deletions(-) create mode 100644 pkg/reporting/app_airgap_test.go create mode 100644 pkg/reporting/preflight_airgap_test.go create mode 100644 pkg/reporting/util_test.go delete mode 100644 pkg/store/kotsstore/reporting_store.go create mode 100644 pkg/util/compress.go diff --git a/pkg/handlers/preflight.go b/pkg/handlers/preflight.go index 5138d6b8af..550545d4a3 100644 --- a/pkg/handlers/preflight.go +++ b/pkg/handlers/preflight.go @@ -366,7 +366,7 @@ func (h *Handler) PreflightsReports(w http.ResponseWriter, r *http.Request) { go func() { if err := reporting.GetReporter().SubmitPreflightData(license, foundApp.ID, clusterID, 0, true, "", false, "", ""); err != nil { - logger.Debugf("failed to send preflights data to replicated app: %v", err) + logger.Debugf("failed to submit preflight data: %v", err) return } }() diff --git a/pkg/k8sutil/kotsadm.go b/pkg/k8sutil/kotsadm.go index a3b625bf50..9df889c23b 100644 --- a/pkg/k8sutil/kotsadm.go +++ b/pkg/k8sutil/kotsadm.go @@ -14,6 +14,7 @@ import ( kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + apimachinerytypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ) @@ -572,3 +573,12 @@ func waitForDeleteServiceAccounts(ctx context.Context, clientset *kubernetes.Cli time.Sleep(time.Second) } } + +func GetKotsadmDeploymentUID(clientset kubernetes.Interface, namespace string) (apimachinerytypes.UID, error) { + deployment, err := clientset.AppsV1().Deployments(namespace).Get(context.TODO(), "kotsadm", metav1.GetOptions{}) + if err != nil { + return "", errors.Wrap(err, "failed to get replicated deployment") + } + + return deployment.ObjectMeta.UID, nil +} diff --git a/pkg/k8sutil/kotsadm_test.go b/pkg/k8sutil/kotsadm_test.go index 455bb34fa4..e77ba36ed7 100644 --- a/pkg/k8sutil/kotsadm_test.go +++ b/pkg/k8sutil/kotsadm_test.go @@ -5,8 +5,10 @@ import ( "testing" "gopkg.in/go-playground/assert.v1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apimachinerytypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" ) @@ -60,3 +62,49 @@ func TestGetKotsadmID(t *testing.T) { }) } } + +func Test_GetKotsadmDeploymentUID(t *testing.T) { + type args struct { + clientset kubernetes.Interface + namespace string + } + tests := []struct { + name string + args args + want apimachinerytypes.UID + wantErr bool + }{ + { + name: "deployment exists", + args: args{ + clientset: fake.NewSimpleClientset(&appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }), + namespace: "default", + }, + want: "test-uid", + }, + { + name: "deployment does not exist", + args: args{ + clientset: fake.NewSimpleClientset(), + namespace: "default", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetKotsadmDeploymentUID(tt.args.clientset, tt.args.namespace) + if (err != nil) != tt.wantErr { + t.Errorf("GetKotsadmDeploymentUID() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/reporting/app.go b/pkg/reporting/app.go index dcb02e12c4..11675278f9 100644 --- a/pkg/reporting/app.go +++ b/pkg/reporting/app.go @@ -47,7 +47,14 @@ func Init() error { } if kotsadm.IsAirgap() { - reporter = &AirgapReporter{} + clientset, err := k8sutil.GetClientset() + if err != nil { + return errors.Wrap(err, "failed to get clientset") + } + reporter = &AirgapReporter{ + clientset: clientset, + store: store.GetStore(), + } } else { reporter = &OnlineReporter{} } diff --git a/pkg/reporting/app_airgap.go b/pkg/reporting/app_airgap.go index fbd7a0f742..681f19b4d9 100644 --- a/pkg/reporting/app_airgap.go +++ b/pkg/reporting/app_airgap.go @@ -1,28 +1,147 @@ package reporting import ( + "context" + "fmt" + "strconv" + "sync" + "time" + "github.com/pkg/errors" - "github.com/replicatedhq/kots/pkg/store" + "github.com/replicatedhq/kots/pkg/api/reporting/types" + "github.com/replicatedhq/kots/pkg/k8sutil" + "github.com/replicatedhq/kots/pkg/logger" + "github.com/replicatedhq/kots/pkg/util" + kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + InstanceReportSecretNameFormat = "kotsadm-%s-instance-report" + InstanceReportSecretKey = "report" + InstanceReportEventLimit = 4000 ) +var instanceReportMtx = sync.Mutex{} + func (r *AirgapReporter) SubmitAppInfo(appID string) error { - a, err := store.GetStore().GetApp(appID) + a, err := r.store.GetApp(appID) if err != nil { - if store.GetStore().IsNotFound(err) { + if r.store.IsNotFound(err) { return nil } - return errors.Wrap(err, "failed to get airgaped app") + return errors.Wrap(err, "failed to get airgapped app") } - license, err := store.GetStore().GetLatestLicenseForApp(a.ID) + license, err := r.store.GetLatestLicenseForApp(a.ID) if err != nil { return errors.Wrap(err, "failed to get license for airgapped app") } reportingInfo := GetReportingInfo(appID) - err = store.GetStore().SaveReportingInfo(license.Spec.LicenseID, reportingInfo) + event := GetInstanceReportEvent(license.Spec.LicenseID, reportingInfo) + + if err := CreateInstanceReportEvent(r.clientset, util.PodNamespace, a.Slug, event); err != nil { + return errors.Wrap(err, "failed to create instance report event") + } + + return nil +} + +func GetInstanceReportEvent(licenseID string, reportingInfo *types.ReportingInfo) InstanceReportEvent { + // not using the "cursor" packages because it doesn't provide access to the underlying int64 + downstreamSequence, err := strconv.ParseUint(reportingInfo.Downstream.Cursor, 10, 64) + if err != nil { + logger.Debugf("failed to parse downstream cursor %q: %v", reportingInfo.Downstream.Cursor, err) + } + + return InstanceReportEvent{ + ReportedAt: time.Now().UTC().UnixMilli(), + LicenseID: licenseID, + InstanceID: reportingInfo.InstanceID, + ClusterID: reportingInfo.ClusterID, + AppStatus: reportingInfo.AppStatus, + IsKurl: reportingInfo.IsKurl, + KurlNodeCountTotal: reportingInfo.KurlNodeCountTotal, + KurlNodeCountReady: reportingInfo.KurlNodeCountReady, + K8sVersion: reportingInfo.K8sVersion, + K8sDistribution: reportingInfo.K8sDistribution, + KotsVersion: reportingInfo.KOTSVersion, + KotsInstallID: reportingInfo.KOTSInstallID, + KurlInstallID: reportingInfo.KURLInstallID, + IsGitOpsEnabled: reportingInfo.IsGitOpsEnabled, + GitOpsProvider: reportingInfo.GitOpsProvider, + DownstreamChannelID: reportingInfo.Downstream.ChannelID, + DownstreamChannelSequence: downstreamSequence, + DownstreamChannelName: reportingInfo.Downstream.ChannelName, + DownstreamSequence: reportingInfo.Downstream.Sequence, + DownstreamSource: reportingInfo.Downstream.Source, + InstallStatus: reportingInfo.Downstream.Status, + PreflightState: reportingInfo.Downstream.PreflightState, + SkipPreflights: reportingInfo.Downstream.SkipPreflights, + ReplHelmInstalls: reportingInfo.Downstream.ReplHelmInstalls, + NativeHelmInstalls: reportingInfo.Downstream.NativeHelmInstalls, + } +} + +func CreateInstanceReportEvent(clientset kubernetes.Interface, namespace string, appSlug string, event InstanceReportEvent) error { + instanceReportMtx.Lock() + defer instanceReportMtx.Unlock() + + existingSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), fmt.Sprintf(InstanceReportSecretNameFormat, appSlug), metav1.GetOptions{}) + if err != nil && !kuberneteserrors.IsNotFound(err) { + return errors.Wrap(err, "failed to get airgap instance report secret") + } else if kuberneteserrors.IsNotFound(err) { + instanceReport := &InstanceReport{ + Events: []InstanceReportEvent{event}, + } + data, err := EncodeAirgapReport(instanceReport) + if err != nil { + return errors.Wrap(err, "failed to encode instance report") + } + + uid, err := k8sutil.GetKotsadmDeploymentUID(clientset, namespace) + if err != nil { + return errors.Wrap(err, "failed to get kotsadm deployment uid") + } + + secret := AirgapReportSecret(fmt.Sprintf(InstanceReportSecretNameFormat, appSlug), namespace, uid, data) + + _, err = clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + return errors.Wrap(err, "failed to create airgap instance report secret") + } + + return nil + } + + if existingSecret.Data == nil { + existingSecret.Data = map[string][]byte{} + } + + existingInstanceReport := &InstanceReport{} + if existingSecret.Data[InstanceReportSecretKey] != nil { + if err := DecodeAirgapReport(existingSecret.Data[InstanceReportSecretKey], existingInstanceReport); err != nil { + return errors.Wrap(err, "failed to load existing instance report") + } + } + + existingInstanceReport.Events = append(existingInstanceReport.Events, event) + if len(existingInstanceReport.Events) > InstanceReportEventLimit { + existingInstanceReport.Events = existingInstanceReport.Events[len(existingInstanceReport.Events)-InstanceReportEventLimit:] + } + + data, err := EncodeAirgapReport(existingInstanceReport) + if err != nil { + return errors.Wrap(err, "failed to encode existing instance report") + } + + existingSecret.Data[InstanceReportSecretKey] = data + + _, err = clientset.CoreV1().Secrets(namespace).Update(context.TODO(), existingSecret, metav1.UpdateOptions{}) if err != nil { - return errors.Wrap(err, "failed to save reporting info") + return errors.Wrap(err, "failed to update airgap instance report secret") } return nil diff --git a/pkg/reporting/app_airgap_test.go b/pkg/reporting/app_airgap_test.go new file mode 100644 index 0000000000..3db438b8d9 --- /dev/null +++ b/pkg/reporting/app_airgap_test.go @@ -0,0 +1,217 @@ +package reporting + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" +) + +func TestCreateInstanceReportEvent(t *testing.T) { + testDownstreamSequence := int64(123) + testEvent := InstanceReportEvent{ + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + AppStatus: "ready", + IsKurl: true, + KurlNodeCountTotal: 3, + KurlNodeCountReady: 3, + K8sVersion: "1.28.0", + K8sDistribution: "kurl", + KotsVersion: "1.100.0", + KotsInstallID: "test-kots-install-id", + KurlInstallID: "test-kurl-install-id", + IsGitOpsEnabled: true, + GitOpsProvider: "test-gitops-provider", + DownstreamChannelID: "test-downstream-channel-id", + DownstreamChannelSequence: 123, + DownstreamChannelName: "test-downstream-channel-name", + DownstreamSequence: &testDownstreamSequence, + DownstreamSource: "test-downstream-source", + InstallStatus: "installed", + PreflightState: "passed", + SkipPreflights: false, + ReplHelmInstalls: 1, + NativeHelmInstalls: 2, + } + + testReportWithOneEvent := &InstanceReport{ + Events: []InstanceReportEvent{testEvent}, + } + testReportWithOneEventData, err := EncodeAirgapReport(testReportWithOneEvent) + require.NoError(t, err) + + testReportWithMaxEvents := &InstanceReport{} + for i := 0; i < InstanceReportEventLimit; i++ { + testReportWithMaxEvents.Events = append(testReportWithMaxEvents.Events, testEvent) + } + testReportWithMaxEventsData, err := EncodeAirgapReport(testReportWithMaxEvents) + require.NoError(t, err) + + type args struct { + clientset kubernetes.Interface + namespace string + appSlug string + event InstanceReportEvent + } + tests := []struct { + name string + args args + wantNumEvents int + wantErr bool + }{ + { + name: "secret does not exist", + args: args{ + clientset: fake.NewSimpleClientset(&appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 1, + }, + { + name: "secret exists with an existing event", + args: args{ + clientset: fake.NewSimpleClientset( + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(InstanceReportSecretNameFormat, "test-app-slug"), + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "kotsadm", + UID: "test-uid", + }, + }, + }, + Data: map[string][]byte{ + InstanceReportSecretKey: testReportWithOneEventData, + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 2, + }, + { + name: "secret exists without data", + args: args{ + clientset: fake.NewSimpleClientset( + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(InstanceReportSecretNameFormat, "test-app-slug"), + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "kotsadm", + UID: "test-uid", + }, + }, + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 1, + }, + { + name: "secret exists with max number of events", + args: args{ + clientset: fake.NewSimpleClientset( + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(InstanceReportSecretNameFormat, "test-app-slug"), + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "kotsadm", + UID: "test-uid", + }, + }, + }, + Data: map[string][]byte{ + InstanceReportSecretKey: testReportWithMaxEventsData, + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: InstanceReportEventLimit, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := require.New(t) + + err := CreateInstanceReportEvent(tt.args.clientset, tt.args.namespace, tt.args.appSlug, tt.args.event) + if tt.wantErr { + req.Error(err) + return + } + req.NoError(err) + + // validate secret exists and has the expected data + secret, err := tt.args.clientset.CoreV1().Secrets(tt.args.namespace).Get(context.TODO(), fmt.Sprintf(InstanceReportSecretNameFormat, tt.args.appSlug), metav1.GetOptions{}) + req.NoError(err) + req.NotNil(secret.Data[InstanceReportSecretKey]) + + report := &InstanceReport{} + err = DecodeAirgapReport(secret.Data[InstanceReportSecretKey], report) + req.NoError(err) + + req.Len(report.Events, tt.wantNumEvents) + + for _, event := range report.Events { + req.Equal(testEvent, event) + } + }) + } +} diff --git a/pkg/reporting/preflight.go b/pkg/reporting/preflight.go index 29fc6cab11..d13f13e37e 100644 --- a/pkg/reporting/preflight.go +++ b/pkg/reporting/preflight.go @@ -82,7 +82,7 @@ func WaitAndReportPreflightChecks(appID string, sequence int64, isSkipPreflights } if err := GetReporter().SubmitPreflightData(license, appID, clusterID, sequence, isSkipPreflights, currentVersionStatus, isCLI, preflightState, string(appStatus)); err != nil { - logger.Debugf("failed to send preflights data to replicated app: %v", err) + logger.Debugf("failed to submit preflight data: %v", err) return } }() diff --git a/pkg/reporting/preflight_airgap.go b/pkg/reporting/preflight_airgap.go index 913bf43f7f..33285bbbc3 100644 --- a/pkg/reporting/preflight_airgap.go +++ b/pkg/reporting/preflight_airgap.go @@ -1,16 +1,42 @@ package reporting import ( + "context" + "fmt" + "sync" + "time" + "github.com/pkg/errors" - reportingtypes "github.com/replicatedhq/kots/pkg/api/reporting/types" "github.com/replicatedhq/kots/pkg/buildversion" - "github.com/replicatedhq/kots/pkg/store" + "github.com/replicatedhq/kots/pkg/k8sutil" storetypes "github.com/replicatedhq/kots/pkg/store/types" + "github.com/replicatedhq/kots/pkg/util" kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" + kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + PreflightReportSecretNameFormat = "kotsadm-%s-preflight-report" + PreflightReportSecretKey = "report" + PreflightReportEventLimit = 4000 ) +var preflightReportMtx = sync.Mutex{} + func (r *AirgapReporter) SubmitPreflightData(license *kotsv1beta1.License, appID string, clusterID string, sequence int64, skipPreflights bool, installStatus storetypes.DownstreamVersionStatus, isCLI bool, preflightStatus string, appStatus string) error { - status := &reportingtypes.PreflightStatus{ + app, err := r.store.GetApp(appID) + if err != nil { + if r.store.IsNotFound(err) { + return nil + } + return errors.Wrap(err, "failed to get airgapped app") + } + + event := PreflightReportEvent{ + ReportedAt: time.Now().UTC().UnixMilli(), + LicenseID: license.Spec.LicenseID, InstanceID: appID, ClusterID: clusterID, Sequence: sequence, @@ -18,12 +44,74 @@ func (r *AirgapReporter) SubmitPreflightData(license *kotsv1beta1.License, appID InstallStatus: string(installStatus), IsCLI: isCLI, PreflightStatus: preflightStatus, - AppStatus: preflightStatus, - KOTSVersion: buildversion.Version(), + AppStatus: appStatus, + KotsVersion: buildversion.Version(), + } + + if err := CreatePreflightReportEvent(r.clientset, util.PodNamespace, app.Slug, event); err != nil { + return errors.Wrap(err, "failed to create preflight report event") + } + + return nil +} + +func CreatePreflightReportEvent(clientset kubernetes.Interface, namespace string, appSlug string, event PreflightReportEvent) error { + preflightReportMtx.Lock() + defer preflightReportMtx.Unlock() + + existingSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), fmt.Sprintf(PreflightReportSecretNameFormat, appSlug), metav1.GetOptions{}) + if err != nil && !kuberneteserrors.IsNotFound(err) { + return errors.Wrap(err, "failed to get airgap preflight report secret") + } else if kuberneteserrors.IsNotFound(err) { + preflightReport := &PreflightReport{ + Events: []PreflightReportEvent{event}, + } + data, err := EncodeAirgapReport(preflightReport) + if err != nil { + return errors.Wrap(err, "failed to encode preflight report") + } + + uid, err := k8sutil.GetKotsadmDeploymentUID(clientset, namespace) + if err != nil { + return errors.Wrap(err, "failed to get kotsadm deployment uid") + } + + secret := AirgapReportSecret(fmt.Sprintf(PreflightReportSecretNameFormat, appSlug), namespace, uid, data) + + _, err = clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + return errors.Wrap(err, "failed to create airgap preflight report secret") + } + + return nil + } + + if existingSecret.Data == nil { + existingSecret.Data = map[string][]byte{} + } + + existingPreflightReport := &PreflightReport{} + if existingSecret.Data[PreflightReportSecretKey] != nil { + if err := DecodeAirgapReport(existingSecret.Data[PreflightReportSecretKey], existingPreflightReport); err != nil { + return errors.Wrap(err, "failed to load existing preflight report") + } } - err := store.GetStore().SavePreflightReport(license.Spec.LicenseID, status) + + existingPreflightReport.Events = append(existingPreflightReport.Events, event) + if len(existingPreflightReport.Events) > PreflightReportEventLimit { + existingPreflightReport.Events = existingPreflightReport.Events[len(existingPreflightReport.Events)-PreflightReportEventLimit:] + } + + data, err := EncodeAirgapReport(existingPreflightReport) + if err != nil { + return errors.Wrap(err, "failed to encode existing preflight report") + } + + existingSecret.Data[PreflightReportSecretKey] = data + + _, err = clientset.CoreV1().Secrets(namespace).Update(context.TODO(), existingSecret, metav1.UpdateOptions{}) if err != nil { - return errors.Wrap(err, "failed to save preflight report") + return errors.Wrap(err, "failed to update airgap preflight report secret") } return nil diff --git a/pkg/reporting/preflight_airgap_test.go b/pkg/reporting/preflight_airgap_test.go new file mode 100644 index 0000000000..22033625c5 --- /dev/null +++ b/pkg/reporting/preflight_airgap_test.go @@ -0,0 +1,202 @@ +package reporting + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" +) + +func TestCreatePreflightReportEvent(t *testing.T) { + testEvent := PreflightReportEvent{ + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + Sequence: 123, + SkipPreflights: false, + InstallStatus: "installed", + IsCLI: true, + PreflightStatus: "pass", + AppStatus: "ready", + KotsVersion: "1.100.0", + } + + testReportWithOneEvent := &PreflightReport{ + Events: []PreflightReportEvent{testEvent}, + } + testReportWithOneEventData, err := EncodeAirgapReport(testReportWithOneEvent) + require.NoError(t, err) + + testReportWithMaxEvents := &PreflightReport{} + for i := 0; i < PreflightReportEventLimit; i++ { + testReportWithMaxEvents.Events = append(testReportWithMaxEvents.Events, testEvent) + } + testReportWithMaxEventsData, err := EncodeAirgapReport(testReportWithMaxEvents) + require.NoError(t, err) + + type args struct { + clientset kubernetes.Interface + namespace string + appSlug string + event PreflightReportEvent + } + tests := []struct { + name string + args args + wantNumEvents int + wantErr bool + }{ + { + name: "secret does not exist", + args: args{ + clientset: fake.NewSimpleClientset(&appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 1, + }, + { + name: "secret exists with an existing event", + args: args{ + clientset: fake.NewSimpleClientset( + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(PreflightReportSecretNameFormat, "test-app-slug"), + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "kotsadm", + UID: "test-uid", + }, + }, + }, + Data: map[string][]byte{ + PreflightReportSecretKey: testReportWithOneEventData, + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 2, + }, + { + name: "secret exists without data", + args: args{ + clientset: fake.NewSimpleClientset( + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(PreflightReportSecretNameFormat, "test-app-slug"), + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "kotsadm", + UID: "test-uid", + }, + }, + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 1, + }, + { + name: "secret exists with max number of events", + args: args{ + clientset: fake.NewSimpleClientset( + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "default", + UID: "test-uid", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(PreflightReportSecretNameFormat, "test-app-slug"), + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "kotsadm", + UID: "test-uid", + }, + }, + }, + Data: map[string][]byte{ + PreflightReportSecretKey: testReportWithMaxEventsData, + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: PreflightReportEventLimit, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := require.New(t) + + err := CreatePreflightReportEvent(tt.args.clientset, tt.args.namespace, tt.args.appSlug, tt.args.event) + if tt.wantErr { + req.Error(err) + return + } + req.NoError(err) + + // validate secret exists and has the expected data + secret, err := tt.args.clientset.CoreV1().Secrets(tt.args.namespace).Get(context.TODO(), fmt.Sprintf(PreflightReportSecretNameFormat, tt.args.appSlug), metav1.GetOptions{}) + req.NoError(err) + req.NotNil(secret.Data[PreflightReportSecretKey]) + + report := &PreflightReport{} + err = DecodeAirgapReport(secret.Data[PreflightReportSecretKey], report) + req.NoError(err) + + req.Len(report.Events, tt.wantNumEvents) + + for _, event := range report.Events { + req.Equal(testEvent, event) + } + }) + } +} diff --git a/pkg/reporting/types.go b/pkg/reporting/types.go index ce96e9e5a0..db3d7b46a2 100644 --- a/pkg/reporting/types.go +++ b/pkg/reporting/types.go @@ -1,8 +1,10 @@ package reporting import ( + "github.com/replicatedhq/kots/pkg/store" storetypes "github.com/replicatedhq/kots/pkg/store/types" kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" + "k8s.io/client-go/kubernetes" ) type Distribution int64 @@ -33,6 +35,8 @@ type Reporter interface { var reporter Reporter type AirgapReporter struct { + clientset kubernetes.Interface + store store.Store } var _ Reporter = &AirgapReporter{} @@ -75,3 +79,53 @@ func (d Distribution) String() string { } return "unknown" } + +type InstanceReport struct { + Events []InstanceReportEvent `json:"events"` +} + +type InstanceReportEvent struct { + ReportedAt int64 `json:"reported_at"` + LicenseID string `json:"license_id"` + InstanceID string `json:"instance_id"` + ClusterID string `json:"cluster_id"` + AppStatus string `json:"app_status"` + IsKurl bool `json:"is_kurl"` + KurlNodeCountTotal int `json:"kurl_node_count_total"` + KurlNodeCountReady int `json:"kurl_node_count_ready"` + K8sVersion string `json:"k8s_version"` + K8sDistribution string `json:"k8s_distribution,omitempty"` + KotsVersion string `json:"kots_version"` + KotsInstallID string `json:"kots_install_id,omitempty"` + KurlInstallID string `json:"kurl_install_id,omitempty"` + IsGitOpsEnabled bool `json:"is_gitops_enabled"` + GitOpsProvider string `json:"gitops_provider"` + DownstreamChannelID string `json:"downstream_channel_id,omitempty"` + DownstreamChannelSequence uint64 `json:"downstream_channel_sequence,omitempty"` + DownstreamChannelName string `json:"downstream_channel_name,omitempty"` + DownstreamSequence *int64 `json:"downstream_sequence,omitempty"` + DownstreamSource string `json:"downstream_source,omitempty"` + InstallStatus string `json:"install_status,omitempty"` + PreflightState string `json:"preflight_state,omitempty"` + SkipPreflights bool `json:"skip_preflights"` + ReplHelmInstalls int `json:"repl_helm_installs"` + NativeHelmInstalls int `json:"native_helm_installs"` +} + +type PreflightReport struct { + Events []PreflightReportEvent `json:"events"` +} + +type PreflightReportEvent struct { + ReportedAt int64 `json:"reported_at"` + LicenseID string `json:"license_id"` + InstanceID string `json:"instance_id"` + ClusterID string `json:"cluster_id"` + Sequence int64 `json:"sequence"` + SkipPreflights bool `json:"skip_preflights"` + InstallStatus string `json:"install_status"` + IsCLI bool `json:"is_cli"` + PreflightStatus string `json:"preflight_status"` + AppStatus string `json:"app_status"` + KotsVersion string `json:"kots_version"` +} diff --git a/pkg/reporting/util.go b/pkg/reporting/util.go index e9da1ba89f..3a3f5c546f 100644 --- a/pkg/reporting/util.go +++ b/pkg/reporting/util.go @@ -1,12 +1,19 @@ package reporting import ( + "encoding/base64" + "encoding/json" "net/http" "os" "regexp" "strconv" + "github.com/pkg/errors" "github.com/replicatedhq/kots/pkg/api/reporting/types" + "github.com/replicatedhq/kots/pkg/util" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apimachinerytypes "k8s.io/apimachinery/pkg/types" ) func InjectReportingInfoHeaders(req *http.Request, reportingInfo *types.ReportingInfo) { @@ -87,3 +94,62 @@ func isDevEndpoint(endpoint string) bool { result, _ := regexp.MatchString(`replicated-app`, endpoint) return result } + +func AirgapReportSecret(name string, namespace string, kotsadmUID apimachinerytypes.UID, data []byte) *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + // since this secret is created by the kotsadm deployment, we should set the owner reference + // so that it is deleted when the kotsadm deployment is deleted + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "kotsadm", + UID: kotsadmUID, + }, + }, + }, + Data: map[string][]byte{ + InstanceReportSecretKey: data, + }, + } +} + +// EncodeAirgapReport marshals, compresses, and base64 encodes the given report +func EncodeAirgapReport(r any) ([]byte, error) { + data, err := json.Marshal(r) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal airgap report") + } + compressedData, err := util.GzipData(data) + if err != nil { + return nil, errors.Wrap(err, "failed to gzip airgap report") + } + encodedData := base64.StdEncoding.EncodeToString(compressedData) + + return []byte(encodedData), nil +} + +// DecodeAirgapReport base64 decodes, uncompresses, and unmarshals the given report +func DecodeAirgapReport(encodedData []byte, r any) error { + decodedData, err := base64.StdEncoding.DecodeString(string(encodedData)) + if err != nil { + return errors.Wrap(err, "failed to decode airgap report") + } + decompressedData, err := util.GunzipData(decodedData) + if err != nil { + return errors.Wrap(err, "failed to gunzip airgap report") + } + + if err := json.Unmarshal(decompressedData, r); err != nil { + return errors.Wrap(err, "failed to unmarshal airgap report") + } + + return nil +} diff --git a/pkg/reporting/util_test.go b/pkg/reporting/util_test.go new file mode 100644 index 0000000000..fe321f2581 --- /dev/null +++ b/pkg/reporting/util_test.go @@ -0,0 +1,82 @@ +package reporting + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_EncodeDecodeAirgapReport(t *testing.T) { + req := require.New(t) + + // instance report + testDownstreamSequence := int64(123) + testInstanceReport := &InstanceReport{ + Events: []InstanceReportEvent{ + { + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + AppStatus: "ready", + IsKurl: true, + KurlNodeCountTotal: 3, + KurlNodeCountReady: 3, + K8sVersion: "1.28.0", + K8sDistribution: "kurl", + KotsVersion: "1.100.0", + KotsInstallID: "test-kots-install-id", + KurlInstallID: "test-kurl-install-id", + IsGitOpsEnabled: true, + GitOpsProvider: "test-gitops-provider", + DownstreamChannelID: "test-downstream-channel-id", + DownstreamChannelSequence: 123, + DownstreamChannelName: "test-downstream-channel-name", + DownstreamSequence: &testDownstreamSequence, + DownstreamSource: "test-downstream-source", + InstallStatus: "installed", + PreflightState: "passed", + SkipPreflights: false, + ReplHelmInstalls: 1, + NativeHelmInstalls: 2, + }, + }, + } + + encodedInstanceReport, err := EncodeAirgapReport(testInstanceReport) + req.NoError(err) + + decodedInstanceReport := &InstanceReport{} + err = DecodeAirgapReport(encodedInstanceReport, decodedInstanceReport) + req.NoError(err) + + req.Equal(testInstanceReport, decodedInstanceReport) + + // preflight report + testPrelightReport := &PreflightReport{ + Events: []PreflightReportEvent{ + { + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + Sequence: 123, + SkipPreflights: false, + InstallStatus: "installed", + IsCLI: true, + PreflightStatus: "pass", + AppStatus: "ready", + KotsVersion: "1.100.0", + }, + }, + } + + encodedPreflightReport, err := EncodeAirgapReport(testPrelightReport) + req.NoError(err) + + decodedPreflightReport := &PreflightReport{} + err = DecodeAirgapReport(encodedPreflightReport, decodedPreflightReport) + req.NoError(err) + + req.Equal(testPrelightReport, decodedPreflightReport) +} diff --git a/pkg/store/kotsstore/reporting_store.go b/pkg/store/kotsstore/reporting_store.go deleted file mode 100644 index d8138af0f0..0000000000 --- a/pkg/store/kotsstore/reporting_store.go +++ /dev/null @@ -1,246 +0,0 @@ -package kotsstore - -import ( - "fmt" - "strconv" - "time" - - "github.com/pkg/errors" - reportingtypes "github.com/replicatedhq/kots/pkg/api/reporting/types" - "github.com/replicatedhq/kots/pkg/logger" - "github.com/replicatedhq/kots/pkg/persistence" - "github.com/rqlite/gorqlite" -) - -func (s *KOTSStore) SavePreflightReport(licenseID string, preflightStatus *reportingtypes.PreflightStatus) error { - db := persistence.MustGetDBSession() - - createdAt := time.Now().UTC() - - query := ` - INSERT INTO preflight_report ( - created_at, - license_id, - instance_id, - cluster_id, - sequence, - skip_preflights, - install_status, - is_cli, - preflight_status, - app_status, - kots_version) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(created_at) DO UPDATE SET - license_id = EXCLUDED.license_id, - instance_id = EXCLUDED.instance_id, - cluster_id = EXCLUDED.cluster_id, - sequence = EXCLUDED.sequence, - skip_preflights = EXCLUDED.skip_preflights, - install_status = EXCLUDED.install_status, - is_cli = EXCLUDED.is_cli, - preflight_status = EXCLUDED.preflight_status, - app_status = EXCLUDED.app_status, - kots_version = EXCLUDED.kots_version` - - statement := gorqlite.ParameterizedStatement{ - Query: query, - Arguments: []interface{}{ - createdAt.UnixMilli(), - licenseID, - preflightStatus.InstanceID, - preflightStatus.ClusterID, - preflightStatus.Sequence, - preflightStatus.SkipPreflights, - preflightStatus.InstallStatus, - preflightStatus.IsCLI, - preflightStatus.PreflightStatus, - preflightStatus.AppStatus, - preflightStatus.KOTSVersion, - }, - } - - wr, err := db.WriteOneParameterized(statement) - if err != nil { - return fmt.Errorf("failed to write preflight report: %v: %v", err, wr.Err) - } - - go func() { - err := s.removeOldReportingData("preflight_report") - if err != nil { - logger.Warnf("failed to delete old data from preflight_report: %v", err) - } - }() - - return nil -} - -func (s *KOTSStore) SaveReportingInfo(licenseID string, reportingInfo *reportingtypes.ReportingInfo) error { - db := persistence.MustGetDBSession() - - createdAt := time.Now().UTC() - - // not using the "cursor" packages because it doesn't provide access to the underlying int64 - downstreamSequence, err := strconv.ParseUint(reportingInfo.Downstream.Cursor, 10, 64) - if err != nil { - logger.Debugf("failed to parse downstream cursor %q: %v", reportingInfo.Downstream.Cursor, err) - } - - query := ` - INSERT INTO instance_report ( - created_at, - license_id, - instance_id, - cluster_id, - app_status, - is_kurl, - kurl_node_count_total, - kurl_node_count_ready, - k8s_version, - kots_version, - kots_install_id, - kurl_install_id, - is_gitops_enabled, - gitops_provider, - downstream_channel_sequence, - downstream_channel_id, - downstream_channel_name, - downstream_sequence, - downstream_source, - install_status, - preflight_state, - skip_preflights, - repl_helm_installs, - native_helm_installs) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(created_at) DO UPDATE SET - license_id = EXCLUDED.license_id, - instance_id = EXCLUDED.instance_id, - cluster_id = EXCLUDED.cluster_id, - app_status = EXCLUDED.app_status, - is_kurl = EXCLUDED.is_kurl, - kurl_node_count_total = EXCLUDED.kurl_node_count_total, - kurl_node_count_ready = EXCLUDED.kurl_node_count_ready, - k8s_version = EXCLUDED.k8s_version, - kots_version = EXCLUDED.kots_version, - kots_install_id = EXCLUDED.kots_install_id, - kurl_install_id = EXCLUDED.kurl_install_id, - is_gitops_enabled = EXCLUDED.is_gitops_enabled, - gitops_provider = EXCLUDED.gitops_provider, - downstream_channel_sequence = EXCLUDED.downstream_channel_sequence, - downstream_channel_id = EXCLUDED.downstream_channel_id, - downstream_channel_name = EXCLUDED.downstream_channel_name, - downstream_sequence = EXCLUDED.downstream_sequence, - downstream_source = EXCLUDED.downstream_source, - install_status = EXCLUDED.install_status, - preflight_state = EXCLUDED.preflight_state, - skip_preflights = EXCLUDED.skip_preflights, - repl_helm_installs = EXCLUDED.repl_helm_installs, - native_helm_installs = EXCLUDED.native_helm_installs` - - statement := gorqlite.ParameterizedStatement{ - Query: query, - Arguments: []interface{}{ - createdAt.UnixMilli(), - licenseID, - reportingInfo.InstanceID, - reportingInfo.ClusterID, - reportingInfo.AppStatus, - reportingInfo.IsKurl, - reportingInfo.KurlNodeCountTotal, - reportingInfo.KurlNodeCountReady, - reportingInfo.K8sVersion, - reportingInfo.KOTSVersion, - reportingInfo.KOTSInstallID, - reportingInfo.KURLInstallID, - reportingInfo.IsGitOpsEnabled, - reportingInfo.GitOpsProvider, - downstreamSequence, - reportingInfo.Downstream.ChannelID, - reportingInfo.Downstream.ChannelName, - reportingInfo.Downstream.Sequence, - reportingInfo.Downstream.Source, - reportingInfo.Downstream.Status, - reportingInfo.Downstream.PreflightState, - reportingInfo.Downstream.SkipPreflights, - reportingInfo.Downstream.ReplHelmInstalls, - reportingInfo.Downstream.NativeHelmInstalls, - }, - } - - wr, err := db.WriteOneParameterized(statement) - if err != nil { - return fmt.Errorf("failed to write instance report: %v: %v", err, wr.Err) - } - - go func() { - err := s.removeOldReportingData("instance_report") - if err != nil { - logger.Warnf("failed to delete old data from instance_report: %v", err) - } - }() - - return nil -} - -func (s *KOTSStore) removeOldReportingData(reportingTable string) error { - db := persistence.MustGetDBSession() - - query := fmt.Sprintf(`select count(1) from %s`, reportingTable) - rows, err := db.QueryOneParameterized(gorqlite.ParameterizedStatement{ - Query: query, - }) - if err != nil { - return fmt.Errorf("failed to query number of rows: %v: %v", err, rows.Err) - } - - if !rows.Next() { - return ErrNotFound - } - - var numRows int64 - if err := rows.Scan(&numRows); err != nil { - return errors.Wrap(err, "failed to scan number of rows") - } - - reportingMaxRows := int64(4000) // at 10 records per day, this is more than a year of data - if numRows <= reportingMaxRows { - logger.Debugf("no old data to delete from %s", reportingTable) - return nil - } - - query = fmt.Sprintf(`select created_at from %s order by created_at desc limit ?, 1`, reportingTable) - rows, err = db.QueryOneParameterized(gorqlite.ParameterizedStatement{ - Query: query, - Arguments: []interface{}{ - reportingMaxRows, - }, - }) - if err != nil { - return fmt.Errorf("failed to query timestamp: %v: %v", err, rows.Err) - } - - if !rows.Next() { - return ErrNotFound - } - - // timestamps are stored with millisecond precision, but scanning directly into a Time variable assumes second precision - var timeMs int64 - if err := rows.Scan(&timeMs); err != nil { - return errors.Wrap(err, "failed to scan timestamp") - } - oldestCreatedAt := time.UnixMilli(timeMs) - - query = fmt.Sprintf(`delete from %s where created_at <= ?`, reportingTable) - wr, err := db.WriteOneParameterized(gorqlite.ParameterizedStatement{ - Query: query, - Arguments: []interface{}{ - oldestCreatedAt.UnixMilli(), - }, - }) - if err != nil { - return fmt.Errorf("failed to delete: %v: %v", err, wr.Err) - } - - return nil -} diff --git a/pkg/store/mock/mock.go b/pkg/store/mock/mock.go index a71c1b9f57..7a0eacccc2 100644 --- a/pkg/store/mock/mock.go +++ b/pkg/store/mock/mock.go @@ -12,21 +12,20 @@ import ( gomock "github.com/golang/mock/gomock" types "github.com/replicatedhq/kots/pkg/airgap/types" types0 "github.com/replicatedhq/kots/pkg/api/downstream/types" - types1 "github.com/replicatedhq/kots/pkg/api/reporting/types" - types2 "github.com/replicatedhq/kots/pkg/api/version/types" - types3 "github.com/replicatedhq/kots/pkg/app/types" - types4 "github.com/replicatedhq/kots/pkg/appstate/types" - types5 "github.com/replicatedhq/kots/pkg/gitops/types" - types6 "github.com/replicatedhq/kots/pkg/kotsadmsnapshot/types" - types7 "github.com/replicatedhq/kots/pkg/online/types" - types8 "github.com/replicatedhq/kots/pkg/preflight/types" - types9 "github.com/replicatedhq/kots/pkg/registry/types" - types10 "github.com/replicatedhq/kots/pkg/render/types" - types11 "github.com/replicatedhq/kots/pkg/session/types" - types12 "github.com/replicatedhq/kots/pkg/store/types" - types13 "github.com/replicatedhq/kots/pkg/supportbundle/types" - types14 "github.com/replicatedhq/kots/pkg/upstream/types" - types15 "github.com/replicatedhq/kots/pkg/user/types" + types1 "github.com/replicatedhq/kots/pkg/api/version/types" + types2 "github.com/replicatedhq/kots/pkg/app/types" + types3 "github.com/replicatedhq/kots/pkg/appstate/types" + types4 "github.com/replicatedhq/kots/pkg/gitops/types" + types5 "github.com/replicatedhq/kots/pkg/kotsadmsnapshot/types" + types6 "github.com/replicatedhq/kots/pkg/online/types" + types7 "github.com/replicatedhq/kots/pkg/preflight/types" + types8 "github.com/replicatedhq/kots/pkg/registry/types" + types9 "github.com/replicatedhq/kots/pkg/render/types" + types10 "github.com/replicatedhq/kots/pkg/session/types" + types11 "github.com/replicatedhq/kots/pkg/store/types" + types12 "github.com/replicatedhq/kots/pkg/supportbundle/types" + types13 "github.com/replicatedhq/kots/pkg/upstream/types" + types14 "github.com/replicatedhq/kots/pkg/user/types" v1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" redact "github.com/replicatedhq/troubleshoot/pkg/redact" ) @@ -111,10 +110,10 @@ func (mr *MockStoreMockRecorder) ClearTaskStatus(taskID interface{}) *gomock.Cal } // CreateApp mocks base method. -func (m *MockStore) CreateApp(name, upstreamURI, licenseData string, isAirgapEnabled, skipImagePush, registryIsReadOnly bool) (*types3.App, error) { +func (m *MockStore) CreateApp(name, upstreamURI, licenseData string, isAirgapEnabled, skipImagePush, registryIsReadOnly bool) (*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateApp", name, upstreamURI, licenseData, isAirgapEnabled, skipImagePush, registryIsReadOnly) - ret0, _ := ret[0].(*types3.App) + ret0, _ := ret[0].(*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -126,7 +125,7 @@ func (mr *MockStoreMockRecorder) CreateApp(name, upstreamURI, licenseData, isAir } // CreateAppVersion mocks base method. -func (m *MockStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir, source string, skipPreflights bool, gitops types5.DownstreamGitOps, renderer types10.Renderer) (int64, error) { +func (m *MockStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir, source string, skipPreflights bool, gitops types4.DownstreamGitOps, renderer types9.Renderer) (int64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateAppVersion", appID, baseSequence, filesInDir, source, skipPreflights, gitops, renderer) ret0, _ := ret[0].(int64) @@ -155,7 +154,7 @@ func (mr *MockStoreMockRecorder) CreateAppVersionArchive(appID, sequence, archiv } // CreateInProgressSupportBundle mocks base method. -func (m *MockStore) CreateInProgressSupportBundle(supportBundle *types13.SupportBundle) error { +func (m *MockStore) CreateInProgressSupportBundle(supportBundle *types12.SupportBundle) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateInProgressSupportBundle", supportBundle) ret0, _ := ret[0].(error) @@ -199,7 +198,7 @@ func (mr *MockStoreMockRecorder) CreateNewCluster(userID, isAllUsers, title, tok } // CreatePendingDownloadAppVersion mocks base method. -func (m *MockStore) CreatePendingDownloadAppVersion(appID string, update types14.Update, kotsApplication *v1beta1.Application, license *v1beta1.License) (int64, error) { +func (m *MockStore) CreatePendingDownloadAppVersion(appID string, update types13.Update, kotsApplication *v1beta1.Application, license *v1beta1.License) (int64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreatePendingDownloadAppVersion", appID, update, kotsApplication, license) ret0, _ := ret[0].(int64) @@ -242,10 +241,10 @@ func (mr *MockStoreMockRecorder) CreateScheduledSnapshot(snapshotID, appID, time } // CreateSession mocks base method. -func (m *MockStore) CreateSession(user *types15.User, issuedAt, expiresAt time.Time, roles []string) (*types11.Session, error) { +func (m *MockStore) CreateSession(user *types14.User, issuedAt, expiresAt time.Time, roles []string) (*types10.Session, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateSession", user, issuedAt, expiresAt, roles) - ret0, _ := ret[0].(*types11.Session) + ret0, _ := ret[0].(*types10.Session) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -257,10 +256,10 @@ func (mr *MockStoreMockRecorder) CreateSession(user, issuedAt, expiresAt, roles } // CreateSupportBundle mocks base method. -func (m *MockStore) CreateSupportBundle(bundleID, appID, archivePath string, marshalledTree []byte) (*types13.SupportBundle, error) { +func (m *MockStore) CreateSupportBundle(bundleID, appID, archivePath string, marshalledTree []byte) (*types12.SupportBundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateSupportBundle", bundleID, appID, archivePath, marshalledTree) - ret0, _ := ret[0].(*types13.SupportBundle) + ret0, _ := ret[0].(*types12.SupportBundle) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -429,10 +428,10 @@ func (mr *MockStoreMockRecorder) GetAllAppLicenses() *gomock.Call { } // GetApp mocks base method. -func (m *MockStore) GetApp(appID string) (*types3.App, error) { +func (m *MockStore) GetApp(appID string) (*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetApp", appID) - ret0, _ := ret[0].(*types3.App) + ret0, _ := ret[0].(*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -444,10 +443,10 @@ func (mr *MockStoreMockRecorder) GetApp(appID interface{}) *gomock.Call { } // GetAppFromSlug mocks base method. -func (m *MockStore) GetAppFromSlug(slug string) (*types3.App, error) { +func (m *MockStore) GetAppFromSlug(slug string) (*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAppFromSlug", slug) - ret0, _ := ret[0].(*types3.App) + ret0, _ := ret[0].(*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -489,10 +488,10 @@ func (mr *MockStoreMockRecorder) GetAppIDsFromRegistry(hostname interface{}) *go } // GetAppStatus mocks base method. -func (m *MockStore) GetAppStatus(appID string) (*types4.AppStatus, error) { +func (m *MockStore) GetAppStatus(appID string) (*types3.AppStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAppStatus", appID) - ret0, _ := ret[0].(*types4.AppStatus) + ret0, _ := ret[0].(*types3.AppStatus) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -504,10 +503,10 @@ func (mr *MockStoreMockRecorder) GetAppStatus(appID interface{}) *gomock.Call { } // GetAppVersion mocks base method. -func (m *MockStore) GetAppVersion(appID string, sequence int64) (*types2.AppVersion, error) { +func (m *MockStore) GetAppVersion(appID string, sequence int64) (*types1.AppVersion, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAppVersion", appID, sequence) - ret0, _ := ret[0].(*types2.AppVersion) + ret0, _ := ret[0].(*types1.AppVersion) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -714,10 +713,10 @@ func (mr *MockStoreMockRecorder) GetDownstreamVersionSource(appID, sequence inte } // GetDownstreamVersionStatus mocks base method. -func (m *MockStore) GetDownstreamVersionStatus(appID string, sequence int64) (types12.DownstreamVersionStatus, error) { +func (m *MockStore) GetDownstreamVersionStatus(appID string, sequence int64) (types11.DownstreamVersionStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDownstreamVersionStatus", appID, sequence) - ret0, _ := ret[0].(types12.DownstreamVersionStatus) + ret0, _ := ret[0].(types11.DownstreamVersionStatus) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -956,10 +955,10 @@ func (mr *MockStoreMockRecorder) GetPendingAirgapUploadApp() *gomock.Call { } // GetPendingInstallationStatus mocks base method. -func (m *MockStore) GetPendingInstallationStatus() (*types7.InstallStatus, error) { +func (m *MockStore) GetPendingInstallationStatus() (*types6.InstallStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPendingInstallationStatus") - ret0, _ := ret[0].(*types7.InstallStatus) + ret0, _ := ret[0].(*types6.InstallStatus) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -986,10 +985,10 @@ func (mr *MockStoreMockRecorder) GetPreflightProgress(appID, sequence interface{ } // GetPreflightResults mocks base method. -func (m *MockStore) GetPreflightResults(appID string, sequence int64) (*types8.PreflightResult, error) { +func (m *MockStore) GetPreflightResults(appID string, sequence int64) (*types7.PreflightResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPreflightResults", appID, sequence) - ret0, _ := ret[0].(*types8.PreflightResult) + ret0, _ := ret[0].(*types7.PreflightResult) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1046,10 +1045,10 @@ func (mr *MockStoreMockRecorder) GetRedactions(bundleID interface{}) *gomock.Cal } // GetRegistryDetailsForApp mocks base method. -func (m *MockStore) GetRegistryDetailsForApp(appID string) (types9.RegistrySettings, error) { +func (m *MockStore) GetRegistryDetailsForApp(appID string) (types8.RegistrySettings, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRegistryDetailsForApp", appID) - ret0, _ := ret[0].(types9.RegistrySettings) + ret0, _ := ret[0].(types8.RegistrySettings) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1061,10 +1060,10 @@ func (mr *MockStoreMockRecorder) GetRegistryDetailsForApp(appID interface{}) *go } // GetSession mocks base method. -func (m *MockStore) GetSession(sessionID string) (*types11.Session, error) { +func (m *MockStore) GetSession(sessionID string) (*types10.Session, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSession", sessionID) - ret0, _ := ret[0].(*types11.Session) + ret0, _ := ret[0].(*types10.Session) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1091,10 +1090,10 @@ func (mr *MockStoreMockRecorder) GetSharedPasswordBcrypt() *gomock.Call { } // GetStatusForVersion mocks base method. -func (m *MockStore) GetStatusForVersion(appID, clusterID string, sequence int64) (types12.DownstreamVersionStatus, error) { +func (m *MockStore) GetStatusForVersion(appID, clusterID string, sequence int64) (types11.DownstreamVersionStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetStatusForVersion", appID, clusterID, sequence) - ret0, _ := ret[0].(types12.DownstreamVersionStatus) + ret0, _ := ret[0].(types11.DownstreamVersionStatus) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1106,10 +1105,10 @@ func (mr *MockStoreMockRecorder) GetStatusForVersion(appID, clusterID, sequence } // GetSupportBundle mocks base method. -func (m *MockStore) GetSupportBundle(bundleID string) (*types13.SupportBundle, error) { +func (m *MockStore) GetSupportBundle(bundleID string) (*types12.SupportBundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSupportBundle", bundleID) - ret0, _ := ret[0].(*types13.SupportBundle) + ret0, _ := ret[0].(*types12.SupportBundle) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1121,10 +1120,10 @@ func (mr *MockStoreMockRecorder) GetSupportBundle(bundleID interface{}) *gomock. } // GetSupportBundleAnalysis mocks base method. -func (m *MockStore) GetSupportBundleAnalysis(bundleID string) (*types13.SupportBundleAnalysis, error) { +func (m *MockStore) GetSupportBundleAnalysis(bundleID string) (*types12.SupportBundleAnalysis, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSupportBundleAnalysis", bundleID) - ret0, _ := ret[0].(*types13.SupportBundleAnalysis) + ret0, _ := ret[0].(*types12.SupportBundleAnalysis) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1316,7 +1315,7 @@ func (mr *MockStoreMockRecorder) IsRollbackSupportedForVersion(appID, sequence i } // IsSnapshotsSupportedForVersion mocks base method. -func (m *MockStore) IsSnapshotsSupportedForVersion(a *types3.App, sequence int64, renderer types10.Renderer) (bool, error) { +func (m *MockStore) IsSnapshotsSupportedForVersion(a *types2.App, sequence int64, renderer types9.Renderer) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsSnapshotsSupportedForVersion", a, sequence, renderer) ret0, _ := ret[0].(bool) @@ -1331,10 +1330,10 @@ func (mr *MockStoreMockRecorder) IsSnapshotsSupportedForVersion(a, sequence, ren } // ListAppsForDownstream mocks base method. -func (m *MockStore) ListAppsForDownstream(clusterID string) ([]*types3.App, error) { +func (m *MockStore) ListAppsForDownstream(clusterID string) ([]*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListAppsForDownstream", clusterID) - ret0, _ := ret[0].([]*types3.App) + ret0, _ := ret[0].([]*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1376,10 +1375,10 @@ func (mr *MockStoreMockRecorder) ListDownstreamsForApp(appID interface{}) *gomoc } // ListFailedApps mocks base method. -func (m *MockStore) ListFailedApps() ([]*types3.App, error) { +func (m *MockStore) ListFailedApps() ([]*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListFailedApps") - ret0, _ := ret[0].([]*types3.App) + ret0, _ := ret[0].([]*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1406,10 +1405,10 @@ func (mr *MockStoreMockRecorder) ListInstalledAppSlugs() *gomock.Call { } // ListInstalledApps mocks base method. -func (m *MockStore) ListInstalledApps() ([]*types3.App, error) { +func (m *MockStore) ListInstalledApps() ([]*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListInstalledApps") - ret0, _ := ret[0].([]*types3.App) + ret0, _ := ret[0].([]*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1421,10 +1420,10 @@ func (mr *MockStoreMockRecorder) ListInstalledApps() *gomock.Call { } // ListPendingScheduledInstanceSnapshots mocks base method. -func (m *MockStore) ListPendingScheduledInstanceSnapshots(clusterID string) ([]types6.ScheduledInstanceSnapshot, error) { +func (m *MockStore) ListPendingScheduledInstanceSnapshots(clusterID string) ([]types5.ScheduledInstanceSnapshot, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListPendingScheduledInstanceSnapshots", clusterID) - ret0, _ := ret[0].([]types6.ScheduledInstanceSnapshot) + ret0, _ := ret[0].([]types5.ScheduledInstanceSnapshot) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1436,10 +1435,10 @@ func (mr *MockStoreMockRecorder) ListPendingScheduledInstanceSnapshots(clusterID } // ListPendingScheduledSnapshots mocks base method. -func (m *MockStore) ListPendingScheduledSnapshots(appID string) ([]types6.ScheduledSnapshot, error) { +func (m *MockStore) ListPendingScheduledSnapshots(appID string) ([]types5.ScheduledSnapshot, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListPendingScheduledSnapshots", appID) - ret0, _ := ret[0].([]types6.ScheduledSnapshot) + ret0, _ := ret[0].([]types5.ScheduledSnapshot) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1451,10 +1450,10 @@ func (mr *MockStoreMockRecorder) ListPendingScheduledSnapshots(appID interface{} } // ListSupportBundles mocks base method. -func (m *MockStore) ListSupportBundles(appID string) ([]*types13.SupportBundle, error) { +func (m *MockStore) ListSupportBundles(appID string) ([]*types12.SupportBundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListSupportBundles", appID) - ret0, _ := ret[0].([]*types13.SupportBundle) + ret0, _ := ret[0].([]*types12.SupportBundle) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1533,34 +1532,6 @@ func (mr *MockStoreMockRecorder) RunMigrations() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunMigrations", reflect.TypeOf((*MockStore)(nil).RunMigrations)) } -// SavePreflightReport mocks base method. -func (m *MockStore) SavePreflightReport(licenseID string, preflightStatus *types1.PreflightStatus) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SavePreflightReport", licenseID, preflightStatus) - ret0, _ := ret[0].(error) - return ret0 -} - -// SavePreflightReport indicates an expected call of SavePreflightReport. -func (mr *MockStoreMockRecorder) SavePreflightReport(licenseID, preflightStatus interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SavePreflightReport", reflect.TypeOf((*MockStore)(nil).SavePreflightReport), licenseID, preflightStatus) -} - -// SaveReportingInfo mocks base method. -func (m *MockStore) SaveReportingInfo(licenseID string, reportingInfo *types1.ReportingInfo) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SaveReportingInfo", licenseID, reportingInfo) - ret0, _ := ret[0].(error) - return ret0 -} - -// SaveReportingInfo indicates an expected call of SaveReportingInfo. -func (mr *MockStoreMockRecorder) SaveReportingInfo(licenseID, reportingInfo interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveReportingInfo", reflect.TypeOf((*MockStore)(nil).SaveReportingInfo), licenseID, reportingInfo) -} - // SetAppChannelChanged mocks base method. func (m *MockStore) SetAppChannelChanged(appID string, channelChanged bool) error { m.ctrl.T.Helper() @@ -1604,7 +1575,7 @@ func (mr *MockStoreMockRecorder) SetAppIsAirgap(appID, isAirgap interface{}) *go } // SetAppStatus mocks base method. -func (m *MockStore) SetAppStatus(appID string, resourceStates types4.ResourceStates, updatedAt time.Time, sequence int64) error { +func (m *MockStore) SetAppStatus(appID string, resourceStates types3.ResourceStates, updatedAt time.Time, sequence int64) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetAppStatus", appID, resourceStates, updatedAt, sequence) ret0, _ := ret[0].(error) @@ -1618,7 +1589,7 @@ func (mr *MockStoreMockRecorder) SetAppStatus(appID, resourceStates, updatedAt, } // SetAutoDeploy mocks base method. -func (m *MockStore) SetAutoDeploy(appID string, autoDeploy types3.AutoDeploy) error { +func (m *MockStore) SetAutoDeploy(appID string, autoDeploy types2.AutoDeploy) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetAutoDeploy", appID, autoDeploy) ret0, _ := ret[0].(error) @@ -1632,7 +1603,7 @@ func (mr *MockStoreMockRecorder) SetAutoDeploy(appID, autoDeploy interface{}) *g } // SetDownstreamVersionStatus mocks base method. -func (m *MockStore) SetDownstreamVersionStatus(appID string, sequence int64, status types12.DownstreamVersionStatus, statusInfo string) error { +func (m *MockStore) SetDownstreamVersionStatus(appID string, sequence int64, status types11.DownstreamVersionStatus, statusInfo string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetDownstreamVersionStatus", appID, sequence, status, statusInfo) ret0, _ := ret[0].(error) @@ -1857,7 +1828,7 @@ func (mr *MockStoreMockRecorder) SetUpdateCheckerSpec(appID, updateCheckerSpec i } // UpdateAppLicense mocks base method. -func (m *MockStore) UpdateAppLicense(appID string, sequence int64, archiveDir string, newLicense *v1beta1.License, originalLicenseData string, channelChanged, failOnVersionCreate bool, gitops types5.DownstreamGitOps, renderer types10.Renderer) (int64, error) { +func (m *MockStore) UpdateAppLicense(appID string, sequence int64, archiveDir string, newLicense *v1beta1.License, originalLicenseData string, channelChanged, failOnVersionCreate bool, gitops types4.DownstreamGitOps, renderer types9.Renderer) (int64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateAppLicense", appID, sequence, archiveDir, newLicense, originalLicenseData, channelChanged, failOnVersionCreate, gitops, renderer) ret0, _ := ret[0].(int64) @@ -1886,7 +1857,7 @@ func (mr *MockStoreMockRecorder) UpdateAppLicenseSyncNow(appID interface{}) *gom } // UpdateAppVersion mocks base method. -func (m *MockStore) UpdateAppVersion(appID string, sequence int64, baseSequence *int64, filesInDir, source string, skipPreflights bool, gitops types5.DownstreamGitOps, renderer types10.Renderer) error { +func (m *MockStore) UpdateAppVersion(appID string, sequence int64, baseSequence *int64, filesInDir, source string, skipPreflights bool, gitops types4.DownstreamGitOps, renderer types9.Renderer) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateAppVersion", appID, sequence, baseSequence, filesInDir, source, skipPreflights, gitops, renderer) ret0, _ := ret[0].(error) @@ -1998,7 +1969,7 @@ func (mr *MockStoreMockRecorder) UpdateSessionExpiresAt(sessionID, expiresAt int } // UpdateSupportBundle mocks base method. -func (m *MockStore) UpdateSupportBundle(bundle *types13.SupportBundle) error { +func (m *MockStore) UpdateSupportBundle(bundle *types12.SupportBundle) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateSupportBundle", bundle) ret0, _ := ret[0].(error) @@ -2127,10 +2098,10 @@ func (mr *MockRegistryStoreMockRecorder) GetAppIDsFromRegistry(hostname interfac } // GetRegistryDetailsForApp mocks base method. -func (m *MockRegistryStore) GetRegistryDetailsForApp(appID string) (types9.RegistrySettings, error) { +func (m *MockRegistryStore) GetRegistryDetailsForApp(appID string) (types8.RegistrySettings, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRegistryDetailsForApp", appID) - ret0, _ := ret[0].(types9.RegistrySettings) + ret0, _ := ret[0].(types8.RegistrySettings) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2179,7 +2150,7 @@ func (m *MockSupportBundleStore) EXPECT() *MockSupportBundleStoreMockRecorder { } // CreateInProgressSupportBundle mocks base method. -func (m *MockSupportBundleStore) CreateInProgressSupportBundle(supportBundle *types13.SupportBundle) error { +func (m *MockSupportBundleStore) CreateInProgressSupportBundle(supportBundle *types12.SupportBundle) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateInProgressSupportBundle", supportBundle) ret0, _ := ret[0].(error) @@ -2193,10 +2164,10 @@ func (mr *MockSupportBundleStoreMockRecorder) CreateInProgressSupportBundle(supp } // CreateSupportBundle mocks base method. -func (m *MockSupportBundleStore) CreateSupportBundle(bundleID, appID, archivePath string, marshalledTree []byte) (*types13.SupportBundle, error) { +func (m *MockSupportBundleStore) CreateSupportBundle(bundleID, appID, archivePath string, marshalledTree []byte) (*types12.SupportBundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateSupportBundle", bundleID, appID, archivePath, marshalledTree) - ret0, _ := ret[0].(*types13.SupportBundle) + ret0, _ := ret[0].(*types12.SupportBundle) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2237,10 +2208,10 @@ func (mr *MockSupportBundleStoreMockRecorder) GetRedactions(bundleID interface{} } // GetSupportBundle mocks base method. -func (m *MockSupportBundleStore) GetSupportBundle(bundleID string) (*types13.SupportBundle, error) { +func (m *MockSupportBundleStore) GetSupportBundle(bundleID string) (*types12.SupportBundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSupportBundle", bundleID) - ret0, _ := ret[0].(*types13.SupportBundle) + ret0, _ := ret[0].(*types12.SupportBundle) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2252,10 +2223,10 @@ func (mr *MockSupportBundleStoreMockRecorder) GetSupportBundle(bundleID interfac } // GetSupportBundleAnalysis mocks base method. -func (m *MockSupportBundleStore) GetSupportBundleAnalysis(bundleID string) (*types13.SupportBundleAnalysis, error) { +func (m *MockSupportBundleStore) GetSupportBundleAnalysis(bundleID string) (*types12.SupportBundleAnalysis, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSupportBundleAnalysis", bundleID) - ret0, _ := ret[0].(*types13.SupportBundleAnalysis) + ret0, _ := ret[0].(*types12.SupportBundleAnalysis) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2282,10 +2253,10 @@ func (mr *MockSupportBundleStoreMockRecorder) GetSupportBundleArchive(bundleID i } // ListSupportBundles mocks base method. -func (m *MockSupportBundleStore) ListSupportBundles(appID string) ([]*types13.SupportBundle, error) { +func (m *MockSupportBundleStore) ListSupportBundles(appID string) ([]*types12.SupportBundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListSupportBundles", appID) - ret0, _ := ret[0].([]*types13.SupportBundle) + ret0, _ := ret[0].([]*types12.SupportBundle) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2325,7 +2296,7 @@ func (mr *MockSupportBundleStoreMockRecorder) SetSupportBundleAnalysis(bundleID, } // UpdateSupportBundle mocks base method. -func (m *MockSupportBundleStore) UpdateSupportBundle(bundle *types13.SupportBundle) error { +func (m *MockSupportBundleStore) UpdateSupportBundle(bundle *types12.SupportBundle) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateSupportBundle", bundle) ret0, _ := ret[0].(error) @@ -2391,10 +2362,10 @@ func (mr *MockPreflightStoreMockRecorder) GetPreflightProgress(appID, sequence i } // GetPreflightResults mocks base method. -func (m *MockPreflightStore) GetPreflightResults(appID string, sequence int64) (*types8.PreflightResult, error) { +func (m *MockPreflightStore) GetPreflightResults(appID string, sequence int64) (*types7.PreflightResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPreflightResults", appID, sequence) - ret0, _ := ret[0].(*types8.PreflightResult) + ret0, _ := ret[0].(*types7.PreflightResult) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2699,10 +2670,10 @@ func (m *MockSessionStore) EXPECT() *MockSessionStoreMockRecorder { } // CreateSession mocks base method. -func (m *MockSessionStore) CreateSession(user *types15.User, issuedAt, expiresAt time.Time, roles []string) (*types11.Session, error) { +func (m *MockSessionStore) CreateSession(user *types14.User, issuedAt, expiresAt time.Time, roles []string) (*types10.Session, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateSession", user, issuedAt, expiresAt, roles) - ret0, _ := ret[0].(*types11.Session) + ret0, _ := ret[0].(*types10.Session) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2742,10 +2713,10 @@ func (mr *MockSessionStoreMockRecorder) DeleteSession(sessionID interface{}) *go } // GetSession mocks base method. -func (m *MockSessionStore) GetSession(sessionID string) (*types11.Session, error) { +func (m *MockSessionStore) GetSession(sessionID string) (*types10.Session, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSession", sessionID) - ret0, _ := ret[0].(*types11.Session) + ret0, _ := ret[0].(*types10.Session) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2794,10 +2765,10 @@ func (m *MockAppStatusStore) EXPECT() *MockAppStatusStoreMockRecorder { } // GetAppStatus mocks base method. -func (m *MockAppStatusStore) GetAppStatus(appID string) (*types4.AppStatus, error) { +func (m *MockAppStatusStore) GetAppStatus(appID string) (*types3.AppStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAppStatus", appID) - ret0, _ := ret[0].(*types4.AppStatus) + ret0, _ := ret[0].(*types3.AppStatus) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2809,7 +2780,7 @@ func (mr *MockAppStatusStoreMockRecorder) GetAppStatus(appID interface{}) *gomoc } // SetAppStatus mocks base method. -func (m *MockAppStatusStore) SetAppStatus(appID string, resourceStates types4.ResourceStates, updatedAt time.Time, sequence int64) error { +func (m *MockAppStatusStore) SetAppStatus(appID string, resourceStates types3.ResourceStates, updatedAt time.Time, sequence int64) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetAppStatus", appID, resourceStates, updatedAt, sequence) ret0, _ := ret[0].(error) @@ -2860,10 +2831,10 @@ func (mr *MockAppStoreMockRecorder) AddAppToAllDownstreams(appID interface{}) *g } // CreateApp mocks base method. -func (m *MockAppStore) CreateApp(name, upstreamURI, licenseData string, isAirgapEnabled, skipImagePush, registryIsReadOnly bool) (*types3.App, error) { +func (m *MockAppStore) CreateApp(name, upstreamURI, licenseData string, isAirgapEnabled, skipImagePush, registryIsReadOnly bool) (*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateApp", name, upstreamURI, licenseData, isAirgapEnabled, skipImagePush, registryIsReadOnly) - ret0, _ := ret[0].(*types3.App) + ret0, _ := ret[0].(*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2875,10 +2846,10 @@ func (mr *MockAppStoreMockRecorder) CreateApp(name, upstreamURI, licenseData, is } // GetApp mocks base method. -func (m *MockAppStore) GetApp(appID string) (*types3.App, error) { +func (m *MockAppStore) GetApp(appID string) (*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetApp", appID) - ret0, _ := ret[0].(*types3.App) + ret0, _ := ret[0].(*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2890,10 +2861,10 @@ func (mr *MockAppStoreMockRecorder) GetApp(appID interface{}) *gomock.Call { } // GetAppFromSlug mocks base method. -func (m *MockAppStore) GetAppFromSlug(slug string) (*types3.App, error) { +func (m *MockAppStore) GetAppFromSlug(slug string) (*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAppFromSlug", slug) - ret0, _ := ret[0].(*types3.App) + ret0, _ := ret[0].(*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2950,10 +2921,10 @@ func (mr *MockAppStoreMockRecorder) IsGitOpsEnabledForApp(appID interface{}) *go } // ListAppsForDownstream mocks base method. -func (m *MockAppStore) ListAppsForDownstream(clusterID string) ([]*types3.App, error) { +func (m *MockAppStore) ListAppsForDownstream(clusterID string) ([]*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListAppsForDownstream", clusterID) - ret0, _ := ret[0].([]*types3.App) + ret0, _ := ret[0].([]*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -2980,10 +2951,10 @@ func (mr *MockAppStoreMockRecorder) ListDownstreamsForApp(appID interface{}) *go } // ListFailedApps mocks base method. -func (m *MockAppStore) ListFailedApps() ([]*types3.App, error) { +func (m *MockAppStore) ListFailedApps() ([]*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListFailedApps") - ret0, _ := ret[0].([]*types3.App) + ret0, _ := ret[0].([]*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -3010,10 +2981,10 @@ func (mr *MockAppStoreMockRecorder) ListInstalledAppSlugs() *gomock.Call { } // ListInstalledApps mocks base method. -func (m *MockAppStore) ListInstalledApps() ([]*types3.App, error) { +func (m *MockAppStore) ListInstalledApps() ([]*types2.App, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListInstalledApps") - ret0, _ := ret[0].([]*types3.App) + ret0, _ := ret[0].([]*types2.App) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -3067,7 +3038,7 @@ func (mr *MockAppStoreMockRecorder) SetAppInstallState(appID, state interface{}) } // SetAutoDeploy mocks base method. -func (m *MockAppStore) SetAutoDeploy(appID string, autoDeploy types3.AutoDeploy) error { +func (m *MockAppStore) SetAutoDeploy(appID string, autoDeploy types2.AutoDeploy) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetAutoDeploy", appID, autoDeploy) ret0, _ := ret[0].(error) @@ -3293,10 +3264,10 @@ func (mr *MockDownstreamStoreMockRecorder) GetDownstreamVersionSource(appID, seq } // GetDownstreamVersionStatus mocks base method. -func (m *MockDownstreamStore) GetDownstreamVersionStatus(appID string, sequence int64) (types12.DownstreamVersionStatus, error) { +func (m *MockDownstreamStore) GetDownstreamVersionStatus(appID string, sequence int64) (types11.DownstreamVersionStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDownstreamVersionStatus", appID, sequence) - ret0, _ := ret[0].(types12.DownstreamVersionStatus) + ret0, _ := ret[0].(types11.DownstreamVersionStatus) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -3385,10 +3356,10 @@ func (mr *MockDownstreamStoreMockRecorder) GetPreviouslyDeployedSequence(appID, } // GetStatusForVersion mocks base method. -func (m *MockDownstreamStore) GetStatusForVersion(appID, clusterID string, sequence int64) (types12.DownstreamVersionStatus, error) { +func (m *MockDownstreamStore) GetStatusForVersion(appID, clusterID string, sequence int64) (types11.DownstreamVersionStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetStatusForVersion", appID, clusterID, sequence) - ret0, _ := ret[0].(types12.DownstreamVersionStatus) + ret0, _ := ret[0].(types11.DownstreamVersionStatus) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -3445,7 +3416,7 @@ func (mr *MockDownstreamStoreMockRecorder) MarkAsCurrentDownstreamVersion(appID, } // SetDownstreamVersionStatus mocks base method. -func (m *MockDownstreamStore) SetDownstreamVersionStatus(appID string, sequence int64, status types12.DownstreamVersionStatus, statusInfo string) error { +func (m *MockDownstreamStore) SetDownstreamVersionStatus(appID string, sequence int64, status types11.DownstreamVersionStatus, statusInfo string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetDownstreamVersionStatus", appID, sequence, status, statusInfo) ret0, _ := ret[0].(error) @@ -3552,10 +3523,10 @@ func (mr *MockSnapshotStoreMockRecorder) DeletePendingScheduledSnapshots(appID i } // ListPendingScheduledInstanceSnapshots mocks base method. -func (m *MockSnapshotStore) ListPendingScheduledInstanceSnapshots(clusterID string) ([]types6.ScheduledInstanceSnapshot, error) { +func (m *MockSnapshotStore) ListPendingScheduledInstanceSnapshots(clusterID string) ([]types5.ScheduledInstanceSnapshot, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListPendingScheduledInstanceSnapshots", clusterID) - ret0, _ := ret[0].([]types6.ScheduledInstanceSnapshot) + ret0, _ := ret[0].([]types5.ScheduledInstanceSnapshot) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -3567,10 +3538,10 @@ func (mr *MockSnapshotStoreMockRecorder) ListPendingScheduledInstanceSnapshots(c } // ListPendingScheduledSnapshots mocks base method. -func (m *MockSnapshotStore) ListPendingScheduledSnapshots(appID string) ([]types6.ScheduledSnapshot, error) { +func (m *MockSnapshotStore) ListPendingScheduledSnapshots(appID string) ([]types5.ScheduledSnapshot, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListPendingScheduledSnapshots", appID) - ret0, _ := ret[0].([]types6.ScheduledSnapshot) + ret0, _ := ret[0].([]types5.ScheduledSnapshot) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -3633,7 +3604,7 @@ func (m *MockVersionStore) EXPECT() *MockVersionStoreMockRecorder { } // CreateAppVersion mocks base method. -func (m *MockVersionStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir, source string, skipPreflights bool, gitops types5.DownstreamGitOps, renderer types10.Renderer) (int64, error) { +func (m *MockVersionStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir, source string, skipPreflights bool, gitops types4.DownstreamGitOps, renderer types9.Renderer) (int64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateAppVersion", appID, baseSequence, filesInDir, source, skipPreflights, gitops, renderer) ret0, _ := ret[0].(int64) @@ -3662,7 +3633,7 @@ func (mr *MockVersionStoreMockRecorder) CreateAppVersionArchive(appID, sequence, } // CreatePendingDownloadAppVersion mocks base method. -func (m *MockVersionStore) CreatePendingDownloadAppVersion(appID string, update types14.Update, kotsApplication *v1beta1.Application, license *v1beta1.License) (int64, error) { +func (m *MockVersionStore) CreatePendingDownloadAppVersion(appID string, update types13.Update, kotsApplication *v1beta1.Application, license *v1beta1.License) (int64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreatePendingDownloadAppVersion", appID, update, kotsApplication, license) ret0, _ := ret[0].(int64) @@ -3677,10 +3648,10 @@ func (mr *MockVersionStoreMockRecorder) CreatePendingDownloadAppVersion(appID, u } // GetAppVersion mocks base method. -func (m *MockVersionStore) GetAppVersion(appID string, sequence int64) (*types2.AppVersion, error) { +func (m *MockVersionStore) GetAppVersion(appID string, sequence int64) (*types1.AppVersion, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAppVersion", appID, sequence) - ret0, _ := ret[0].(*types2.AppVersion) + ret0, _ := ret[0].(*types1.AppVersion) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -3842,7 +3813,7 @@ func (mr *MockVersionStoreMockRecorder) IsRollbackSupportedForVersion(appID, seq } // IsSnapshotsSupportedForVersion mocks base method. -func (m *MockVersionStore) IsSnapshotsSupportedForVersion(a *types3.App, sequence int64, renderer types10.Renderer) (bool, error) { +func (m *MockVersionStore) IsSnapshotsSupportedForVersion(a *types2.App, sequence int64, renderer types9.Renderer) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsSnapshotsSupportedForVersion", a, sequence, renderer) ret0, _ := ret[0].(bool) @@ -3857,7 +3828,7 @@ func (mr *MockVersionStoreMockRecorder) IsSnapshotsSupportedForVersion(a, sequen } // UpdateAppVersion mocks base method. -func (m *MockVersionStore) UpdateAppVersion(appID string, sequence int64, baseSequence *int64, filesInDir, source string, skipPreflights bool, gitops types5.DownstreamGitOps, renderer types10.Renderer) error { +func (m *MockVersionStore) UpdateAppVersion(appID string, sequence int64, baseSequence *int64, filesInDir, source string, skipPreflights bool, gitops types4.DownstreamGitOps, renderer types9.Renderer) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateAppVersion", appID, sequence, baseSequence, filesInDir, source, skipPreflights, gitops, renderer) ret0, _ := ret[0].(error) @@ -3967,7 +3938,7 @@ func (mr *MockLicenseStoreMockRecorder) GetLicenseForAppVersion(appID, sequence } // UpdateAppLicense mocks base method. -func (m *MockLicenseStore) UpdateAppLicense(appID string, sequence int64, archiveDir string, newLicense *v1beta1.License, originalLicenseData string, channelChanged, failOnVersionCreate bool, gitops types5.DownstreamGitOps, renderer types10.Renderer) (int64, error) { +func (m *MockLicenseStore) UpdateAppLicense(appID string, sequence int64, archiveDir string, newLicense *v1beta1.License, originalLicenseData string, channelChanged, failOnVersionCreate bool, gitops types4.DownstreamGitOps, renderer types9.Renderer) (int64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateAppLicense", appID, sequence, archiveDir, newLicense, originalLicenseData, channelChanged, failOnVersionCreate, gitops, renderer) ret0, _ := ret[0].(int64) @@ -4211,10 +4182,10 @@ func (m *MockInstallationStore) EXPECT() *MockInstallationStoreMockRecorder { } // GetPendingInstallationStatus mocks base method. -func (m *MockInstallationStore) GetPendingInstallationStatus() (*types7.InstallStatus, error) { +func (m *MockInstallationStore) GetPendingInstallationStatus() (*types6.InstallStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPendingInstallationStatus") - ret0, _ := ret[0].(*types7.InstallStatus) + ret0, _ := ret[0].(*types6.InstallStatus) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -4412,57 +4383,6 @@ func (mr *MockBrandingStoreMockRecorder) GetLatestBrandingForApp(appID interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestBrandingForApp", reflect.TypeOf((*MockBrandingStore)(nil).GetLatestBrandingForApp), appID) } -// MockReportingStore is a mock of ReportingStore interface. -type MockReportingStore struct { - ctrl *gomock.Controller - recorder *MockReportingStoreMockRecorder -} - -// MockReportingStoreMockRecorder is the mock recorder for MockReportingStore. -type MockReportingStoreMockRecorder struct { - mock *MockReportingStore -} - -// NewMockReportingStore creates a new mock instance. -func NewMockReportingStore(ctrl *gomock.Controller) *MockReportingStore { - mock := &MockReportingStore{ctrl: ctrl} - mock.recorder = &MockReportingStoreMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockReportingStore) EXPECT() *MockReportingStoreMockRecorder { - return m.recorder -} - -// SavePreflightReport mocks base method. -func (m *MockReportingStore) SavePreflightReport(licenseID string, preflightStatus *types1.PreflightStatus) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SavePreflightReport", licenseID, preflightStatus) - ret0, _ := ret[0].(error) - return ret0 -} - -// SavePreflightReport indicates an expected call of SavePreflightReport. -func (mr *MockReportingStoreMockRecorder) SavePreflightReport(licenseID, preflightStatus interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SavePreflightReport", reflect.TypeOf((*MockReportingStore)(nil).SavePreflightReport), licenseID, preflightStatus) -} - -// SaveReportingInfo mocks base method. -func (m *MockReportingStore) SaveReportingInfo(licenseID string, reportingInfo *types1.ReportingInfo) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SaveReportingInfo", licenseID, reportingInfo) - ret0, _ := ret[0].(error) - return ret0 -} - -// SaveReportingInfo indicates an expected call of SaveReportingInfo. -func (mr *MockReportingStoreMockRecorder) SaveReportingInfo(licenseID, reportingInfo interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveReportingInfo", reflect.TypeOf((*MockReportingStore)(nil).SaveReportingInfo), licenseID, reportingInfo) -} - // MockEmbeddedClusterStore is a mock of EmbeddedClusterStore interface. type MockEmbeddedClusterStore struct { ctrl *gomock.Controller diff --git a/pkg/store/store_interface.go b/pkg/store/store_interface.go index 34b5872db2..5038c1b6ad 100644 --- a/pkg/store/store_interface.go +++ b/pkg/store/store_interface.go @@ -6,7 +6,6 @@ import ( airgaptypes "github.com/replicatedhq/kots/pkg/airgap/types" downstreamtypes "github.com/replicatedhq/kots/pkg/api/downstream/types" - reportingtypes "github.com/replicatedhq/kots/pkg/api/reporting/types" versiontypes "github.com/replicatedhq/kots/pkg/api/version/types" apptypes "github.com/replicatedhq/kots/pkg/app/types" appstatetypes "github.com/replicatedhq/kots/pkg/appstate/types" @@ -46,7 +45,6 @@ type Store interface { KotsadmParamsStore EmbeddedStore BrandingStore - ReportingStore EmbeddedClusterStore Init() error // this may need options @@ -250,11 +248,6 @@ type BrandingStore interface { GetLatestBrandingForApp(appID string) ([]byte, error) } -type ReportingStore interface { - SavePreflightReport(licenseID string, preflightStatus *reportingtypes.PreflightStatus) error - SaveReportingInfo(licenseID string, reportingInfo *reportingtypes.ReportingInfo) error -} - type EmbeddedClusterStore interface { SetEmbeddedClusterInstallCommandRoles(roles []string) (string, error) GetEmbeddedClusterInstallCommandRoles(token string) ([]string, error) diff --git a/pkg/supportbundle/spec.go b/pkg/supportbundle/spec.go index b958df61b4..181cff1d82 100644 --- a/pkg/supportbundle/spec.go +++ b/pkg/supportbundle/spec.go @@ -805,6 +805,28 @@ func getDefaultDynamicCollectors(app apptypes.AppType, imageName string, pullSec }, }) + collectors = append(collectors, &troubleshootv1beta2.Collect{ + Secret: &troubleshootv1beta2.Secret{ + CollectorMeta: troubleshootv1beta2.CollectorMeta{ + CollectorName: fmt.Sprintf("kotsadm-%s-instance-report", app.GetSlug()), + }, + Name: fmt.Sprintf("kotsadm-%s-instance-report", app.GetSlug()), + Namespace: util.PodNamespace, + Key: "report", + IncludeValue: true, + }, + }, &troubleshootv1beta2.Collect{ + Secret: &troubleshootv1beta2.Secret{ + CollectorMeta: troubleshootv1beta2.CollectorMeta{ + CollectorName: fmt.Sprintf("kotsadm-%s-preflight-report", app.GetSlug()), + }, + Name: fmt.Sprintf("kotsadm-%s-preflight-report", app.GetSlug()), + Namespace: util.PodNamespace, + Key: "report", + IncludeValue: true, + }, + }) + collectors = append(collectors, makeVeleroCollectors()...) if app, ok := app.(*apptypes.App); ok { diff --git a/pkg/util/compress.go b/pkg/util/compress.go new file mode 100644 index 0000000000..e4c1fc1fa6 --- /dev/null +++ b/pkg/util/compress.go @@ -0,0 +1,42 @@ +package util + +import ( + "bytes" + "compress/gzip" + "io" + + "github.com/pkg/errors" +) + +func GzipData(input []byte) ([]byte, error) { + var buf bytes.Buffer + gw := gzip.NewWriter(&buf) + + _, err := gw.Write(input) + if err != nil { + return nil, errors.Wrap(err, "failed to write to gzip writer") + } + + err = gw.Close() + if err != nil { + return nil, errors.Wrap(err, "failed to close gzip writer") + } + + return buf.Bytes(), nil +} + +func GunzipData(input []byte) ([]byte, error) { + r := bytes.NewReader(input) + gr, err := gzip.NewReader(r) + if err != nil { + return nil, errors.Wrap(err, "failed to create gzip reader") + } + defer gr.Close() + + decompressedData, err := io.ReadAll(gr) + if err != nil { + return nil, errors.Wrap(err, "failed to read from gzip reader") + } + + return decompressedData, nil +} From b766b23f5a3e6f15f46cd13611df40c15d0140c4 Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Tue, 24 Oct 2023 17:23:11 +0000 Subject: [PATCH 2/6] address pr feedback --- pkg/k8sutil/kotsadm.go | 10 - pkg/k8sutil/kotsadm_test.go | 48 ----- pkg/reporting/app_airgap.go | 83 +------- pkg/reporting/app_airgap_test.go | 217 -------------------- pkg/reporting/preflight_airgap.go | 81 +------- pkg/reporting/preflight_airgap_test.go | 202 ------------------ pkg/reporting/report.go | 251 ++++++++++++++++++++++ pkg/reporting/report_test.go | 274 +++++++++++++++++++++++++ pkg/reporting/types.go | 50 ----- pkg/reporting/util.go | 66 ------ pkg/reporting/util_test.go | 82 -------- 11 files changed, 530 insertions(+), 834 deletions(-) delete mode 100644 pkg/reporting/app_airgap_test.go delete mode 100644 pkg/reporting/preflight_airgap_test.go create mode 100644 pkg/reporting/report.go create mode 100644 pkg/reporting/report_test.go delete mode 100644 pkg/reporting/util_test.go diff --git a/pkg/k8sutil/kotsadm.go b/pkg/k8sutil/kotsadm.go index 9df889c23b..a3b625bf50 100644 --- a/pkg/k8sutil/kotsadm.go +++ b/pkg/k8sutil/kotsadm.go @@ -14,7 +14,6 @@ import ( kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - apimachinerytypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ) @@ -573,12 +572,3 @@ func waitForDeleteServiceAccounts(ctx context.Context, clientset *kubernetes.Cli time.Sleep(time.Second) } } - -func GetKotsadmDeploymentUID(clientset kubernetes.Interface, namespace string) (apimachinerytypes.UID, error) { - deployment, err := clientset.AppsV1().Deployments(namespace).Get(context.TODO(), "kotsadm", metav1.GetOptions{}) - if err != nil { - return "", errors.Wrap(err, "failed to get replicated deployment") - } - - return deployment.ObjectMeta.UID, nil -} diff --git a/pkg/k8sutil/kotsadm_test.go b/pkg/k8sutil/kotsadm_test.go index e77ba36ed7..455bb34fa4 100644 --- a/pkg/k8sutil/kotsadm_test.go +++ b/pkg/k8sutil/kotsadm_test.go @@ -5,10 +5,8 @@ import ( "testing" "gopkg.in/go-playground/assert.v1" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - apimachinerytypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" ) @@ -62,49 +60,3 @@ func TestGetKotsadmID(t *testing.T) { }) } } - -func Test_GetKotsadmDeploymentUID(t *testing.T) { - type args struct { - clientset kubernetes.Interface - namespace string - } - tests := []struct { - name string - args args - want apimachinerytypes.UID - wantErr bool - }{ - { - name: "deployment exists", - args: args{ - clientset: fake.NewSimpleClientset(&appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }), - namespace: "default", - }, - want: "test-uid", - }, - { - name: "deployment does not exist", - args: args{ - clientset: fake.NewSimpleClientset(), - namespace: "default", - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GetKotsadmDeploymentUID(tt.args.clientset, tt.args.namespace) - if (err != nil) != tt.wantErr { - t.Errorf("GetKotsadmDeploymentUID() error = %v, wantErr %v", err, tt.wantErr) - return - } - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/reporting/app_airgap.go b/pkg/reporting/app_airgap.go index 681f19b4d9..a7c4307ffd 100644 --- a/pkg/reporting/app_airgap.go +++ b/pkg/reporting/app_airgap.go @@ -1,30 +1,15 @@ package reporting import ( - "context" - "fmt" "strconv" - "sync" "time" "github.com/pkg/errors" "github.com/replicatedhq/kots/pkg/api/reporting/types" - "github.com/replicatedhq/kots/pkg/k8sutil" "github.com/replicatedhq/kots/pkg/logger" "github.com/replicatedhq/kots/pkg/util" - kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" ) -const ( - InstanceReportSecretNameFormat = "kotsadm-%s-instance-report" - InstanceReportSecretKey = "report" - InstanceReportEventLimit = 4000 -) - -var instanceReportMtx = sync.Mutex{} - func (r *AirgapReporter) SubmitAppInfo(appID string) error { a, err := r.store.GetApp(appID) if err != nil { @@ -42,21 +27,21 @@ func (r *AirgapReporter) SubmitAppInfo(appID string) error { event := GetInstanceReportEvent(license.Spec.LicenseID, reportingInfo) - if err := CreateInstanceReportEvent(r.clientset, util.PodNamespace, a.Slug, event); err != nil { + if err := CreateReportEvent(r.clientset, util.PodNamespace, a.Slug, event); err != nil { return errors.Wrap(err, "failed to create instance report event") } return nil } -func GetInstanceReportEvent(licenseID string, reportingInfo *types.ReportingInfo) InstanceReportEvent { +func GetInstanceReportEvent(licenseID string, reportingInfo *types.ReportingInfo) *InstanceReportEvent { // not using the "cursor" packages because it doesn't provide access to the underlying int64 downstreamSequence, err := strconv.ParseUint(reportingInfo.Downstream.Cursor, 10, 64) if err != nil { logger.Debugf("failed to parse downstream cursor %q: %v", reportingInfo.Downstream.Cursor, err) } - return InstanceReportEvent{ + return &InstanceReportEvent{ ReportedAt: time.Now().UTC().UnixMilli(), LicenseID: licenseID, InstanceID: reportingInfo.InstanceID, @@ -84,65 +69,3 @@ func GetInstanceReportEvent(licenseID string, reportingInfo *types.ReportingInfo NativeHelmInstalls: reportingInfo.Downstream.NativeHelmInstalls, } } - -func CreateInstanceReportEvent(clientset kubernetes.Interface, namespace string, appSlug string, event InstanceReportEvent) error { - instanceReportMtx.Lock() - defer instanceReportMtx.Unlock() - - existingSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), fmt.Sprintf(InstanceReportSecretNameFormat, appSlug), metav1.GetOptions{}) - if err != nil && !kuberneteserrors.IsNotFound(err) { - return errors.Wrap(err, "failed to get airgap instance report secret") - } else if kuberneteserrors.IsNotFound(err) { - instanceReport := &InstanceReport{ - Events: []InstanceReportEvent{event}, - } - data, err := EncodeAirgapReport(instanceReport) - if err != nil { - return errors.Wrap(err, "failed to encode instance report") - } - - uid, err := k8sutil.GetKotsadmDeploymentUID(clientset, namespace) - if err != nil { - return errors.Wrap(err, "failed to get kotsadm deployment uid") - } - - secret := AirgapReportSecret(fmt.Sprintf(InstanceReportSecretNameFormat, appSlug), namespace, uid, data) - - _, err = clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) - if err != nil { - return errors.Wrap(err, "failed to create airgap instance report secret") - } - - return nil - } - - if existingSecret.Data == nil { - existingSecret.Data = map[string][]byte{} - } - - existingInstanceReport := &InstanceReport{} - if existingSecret.Data[InstanceReportSecretKey] != nil { - if err := DecodeAirgapReport(existingSecret.Data[InstanceReportSecretKey], existingInstanceReport); err != nil { - return errors.Wrap(err, "failed to load existing instance report") - } - } - - existingInstanceReport.Events = append(existingInstanceReport.Events, event) - if len(existingInstanceReport.Events) > InstanceReportEventLimit { - existingInstanceReport.Events = existingInstanceReport.Events[len(existingInstanceReport.Events)-InstanceReportEventLimit:] - } - - data, err := EncodeAirgapReport(existingInstanceReport) - if err != nil { - return errors.Wrap(err, "failed to encode existing instance report") - } - - existingSecret.Data[InstanceReportSecretKey] = data - - _, err = clientset.CoreV1().Secrets(namespace).Update(context.TODO(), existingSecret, metav1.UpdateOptions{}) - if err != nil { - return errors.Wrap(err, "failed to update airgap instance report secret") - } - - return nil -} diff --git a/pkg/reporting/app_airgap_test.go b/pkg/reporting/app_airgap_test.go deleted file mode 100644 index 3db438b8d9..0000000000 --- a/pkg/reporting/app_airgap_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package reporting - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" -) - -func TestCreateInstanceReportEvent(t *testing.T) { - testDownstreamSequence := int64(123) - testEvent := InstanceReportEvent{ - ReportedAt: 1234567890, - LicenseID: "test-license-id", - InstanceID: "test-instance-id", - ClusterID: "test-cluster-id", - AppStatus: "ready", - IsKurl: true, - KurlNodeCountTotal: 3, - KurlNodeCountReady: 3, - K8sVersion: "1.28.0", - K8sDistribution: "kurl", - KotsVersion: "1.100.0", - KotsInstallID: "test-kots-install-id", - KurlInstallID: "test-kurl-install-id", - IsGitOpsEnabled: true, - GitOpsProvider: "test-gitops-provider", - DownstreamChannelID: "test-downstream-channel-id", - DownstreamChannelSequence: 123, - DownstreamChannelName: "test-downstream-channel-name", - DownstreamSequence: &testDownstreamSequence, - DownstreamSource: "test-downstream-source", - InstallStatus: "installed", - PreflightState: "passed", - SkipPreflights: false, - ReplHelmInstalls: 1, - NativeHelmInstalls: 2, - } - - testReportWithOneEvent := &InstanceReport{ - Events: []InstanceReportEvent{testEvent}, - } - testReportWithOneEventData, err := EncodeAirgapReport(testReportWithOneEvent) - require.NoError(t, err) - - testReportWithMaxEvents := &InstanceReport{} - for i := 0; i < InstanceReportEventLimit; i++ { - testReportWithMaxEvents.Events = append(testReportWithMaxEvents.Events, testEvent) - } - testReportWithMaxEventsData, err := EncodeAirgapReport(testReportWithMaxEvents) - require.NoError(t, err) - - type args struct { - clientset kubernetes.Interface - namespace string - appSlug string - event InstanceReportEvent - } - tests := []struct { - name string - args args - wantNumEvents int - wantErr bool - }{ - { - name: "secret does not exist", - args: args{ - clientset: fake.NewSimpleClientset(&appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }), - namespace: "default", - appSlug: "test-app-slug", - event: testEvent, - }, - wantNumEvents: 1, - }, - { - name: "secret exists with an existing event", - args: args{ - clientset: fake.NewSimpleClientset( - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf(InstanceReportSecretNameFormat, "test-app-slug"), - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "kotsadm", - UID: "test-uid", - }, - }, - }, - Data: map[string][]byte{ - InstanceReportSecretKey: testReportWithOneEventData, - }, - }, - ), - namespace: "default", - appSlug: "test-app-slug", - event: testEvent, - }, - wantNumEvents: 2, - }, - { - name: "secret exists without data", - args: args{ - clientset: fake.NewSimpleClientset( - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf(InstanceReportSecretNameFormat, "test-app-slug"), - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "kotsadm", - UID: "test-uid", - }, - }, - }, - }, - ), - namespace: "default", - appSlug: "test-app-slug", - event: testEvent, - }, - wantNumEvents: 1, - }, - { - name: "secret exists with max number of events", - args: args{ - clientset: fake.NewSimpleClientset( - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf(InstanceReportSecretNameFormat, "test-app-slug"), - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "kotsadm", - UID: "test-uid", - }, - }, - }, - Data: map[string][]byte{ - InstanceReportSecretKey: testReportWithMaxEventsData, - }, - }, - ), - namespace: "default", - appSlug: "test-app-slug", - event: testEvent, - }, - wantNumEvents: InstanceReportEventLimit, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req := require.New(t) - - err := CreateInstanceReportEvent(tt.args.clientset, tt.args.namespace, tt.args.appSlug, tt.args.event) - if tt.wantErr { - req.Error(err) - return - } - req.NoError(err) - - // validate secret exists and has the expected data - secret, err := tt.args.clientset.CoreV1().Secrets(tt.args.namespace).Get(context.TODO(), fmt.Sprintf(InstanceReportSecretNameFormat, tt.args.appSlug), metav1.GetOptions{}) - req.NoError(err) - req.NotNil(secret.Data[InstanceReportSecretKey]) - - report := &InstanceReport{} - err = DecodeAirgapReport(secret.Data[InstanceReportSecretKey], report) - req.NoError(err) - - req.Len(report.Events, tt.wantNumEvents) - - for _, event := range report.Events { - req.Equal(testEvent, event) - } - }) - } -} diff --git a/pkg/reporting/preflight_airgap.go b/pkg/reporting/preflight_airgap.go index 33285bbbc3..daf97433ac 100644 --- a/pkg/reporting/preflight_airgap.go +++ b/pkg/reporting/preflight_airgap.go @@ -1,30 +1,15 @@ package reporting import ( - "context" - "fmt" - "sync" "time" "github.com/pkg/errors" "github.com/replicatedhq/kots/pkg/buildversion" - "github.com/replicatedhq/kots/pkg/k8sutil" storetypes "github.com/replicatedhq/kots/pkg/store/types" "github.com/replicatedhq/kots/pkg/util" kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" - kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" ) -const ( - PreflightReportSecretNameFormat = "kotsadm-%s-preflight-report" - PreflightReportSecretKey = "report" - PreflightReportEventLimit = 4000 -) - -var preflightReportMtx = sync.Mutex{} - func (r *AirgapReporter) SubmitPreflightData(license *kotsv1beta1.License, appID string, clusterID string, sequence int64, skipPreflights bool, installStatus storetypes.DownstreamVersionStatus, isCLI bool, preflightStatus string, appStatus string) error { app, err := r.store.GetApp(appID) if err != nil { @@ -34,7 +19,7 @@ func (r *AirgapReporter) SubmitPreflightData(license *kotsv1beta1.License, appID return errors.Wrap(err, "failed to get airgapped app") } - event := PreflightReportEvent{ + event := &PreflightReportEvent{ ReportedAt: time.Now().UTC().UnixMilli(), LicenseID: license.Spec.LicenseID, InstanceID: appID, @@ -48,71 +33,9 @@ func (r *AirgapReporter) SubmitPreflightData(license *kotsv1beta1.License, appID KotsVersion: buildversion.Version(), } - if err := CreatePreflightReportEvent(r.clientset, util.PodNamespace, app.Slug, event); err != nil { + if err := CreateReportEvent(r.clientset, util.PodNamespace, app.Slug, event); err != nil { return errors.Wrap(err, "failed to create preflight report event") } return nil } - -func CreatePreflightReportEvent(clientset kubernetes.Interface, namespace string, appSlug string, event PreflightReportEvent) error { - preflightReportMtx.Lock() - defer preflightReportMtx.Unlock() - - existingSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), fmt.Sprintf(PreflightReportSecretNameFormat, appSlug), metav1.GetOptions{}) - if err != nil && !kuberneteserrors.IsNotFound(err) { - return errors.Wrap(err, "failed to get airgap preflight report secret") - } else if kuberneteserrors.IsNotFound(err) { - preflightReport := &PreflightReport{ - Events: []PreflightReportEvent{event}, - } - data, err := EncodeAirgapReport(preflightReport) - if err != nil { - return errors.Wrap(err, "failed to encode preflight report") - } - - uid, err := k8sutil.GetKotsadmDeploymentUID(clientset, namespace) - if err != nil { - return errors.Wrap(err, "failed to get kotsadm deployment uid") - } - - secret := AirgapReportSecret(fmt.Sprintf(PreflightReportSecretNameFormat, appSlug), namespace, uid, data) - - _, err = clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) - if err != nil { - return errors.Wrap(err, "failed to create airgap preflight report secret") - } - - return nil - } - - if existingSecret.Data == nil { - existingSecret.Data = map[string][]byte{} - } - - existingPreflightReport := &PreflightReport{} - if existingSecret.Data[PreflightReportSecretKey] != nil { - if err := DecodeAirgapReport(existingSecret.Data[PreflightReportSecretKey], existingPreflightReport); err != nil { - return errors.Wrap(err, "failed to load existing preflight report") - } - } - - existingPreflightReport.Events = append(existingPreflightReport.Events, event) - if len(existingPreflightReport.Events) > PreflightReportEventLimit { - existingPreflightReport.Events = existingPreflightReport.Events[len(existingPreflightReport.Events)-PreflightReportEventLimit:] - } - - data, err := EncodeAirgapReport(existingPreflightReport) - if err != nil { - return errors.Wrap(err, "failed to encode existing preflight report") - } - - existingSecret.Data[PreflightReportSecretKey] = data - - _, err = clientset.CoreV1().Secrets(namespace).Update(context.TODO(), existingSecret, metav1.UpdateOptions{}) - if err != nil { - return errors.Wrap(err, "failed to update airgap preflight report secret") - } - - return nil -} diff --git a/pkg/reporting/preflight_airgap_test.go b/pkg/reporting/preflight_airgap_test.go deleted file mode 100644 index 22033625c5..0000000000 --- a/pkg/reporting/preflight_airgap_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package reporting - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" -) - -func TestCreatePreflightReportEvent(t *testing.T) { - testEvent := PreflightReportEvent{ - ReportedAt: 1234567890, - LicenseID: "test-license-id", - InstanceID: "test-instance-id", - ClusterID: "test-cluster-id", - Sequence: 123, - SkipPreflights: false, - InstallStatus: "installed", - IsCLI: true, - PreflightStatus: "pass", - AppStatus: "ready", - KotsVersion: "1.100.0", - } - - testReportWithOneEvent := &PreflightReport{ - Events: []PreflightReportEvent{testEvent}, - } - testReportWithOneEventData, err := EncodeAirgapReport(testReportWithOneEvent) - require.NoError(t, err) - - testReportWithMaxEvents := &PreflightReport{} - for i := 0; i < PreflightReportEventLimit; i++ { - testReportWithMaxEvents.Events = append(testReportWithMaxEvents.Events, testEvent) - } - testReportWithMaxEventsData, err := EncodeAirgapReport(testReportWithMaxEvents) - require.NoError(t, err) - - type args struct { - clientset kubernetes.Interface - namespace string - appSlug string - event PreflightReportEvent - } - tests := []struct { - name string - args args - wantNumEvents int - wantErr bool - }{ - { - name: "secret does not exist", - args: args{ - clientset: fake.NewSimpleClientset(&appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }), - namespace: "default", - appSlug: "test-app-slug", - event: testEvent, - }, - wantNumEvents: 1, - }, - { - name: "secret exists with an existing event", - args: args{ - clientset: fake.NewSimpleClientset( - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf(PreflightReportSecretNameFormat, "test-app-slug"), - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "kotsadm", - UID: "test-uid", - }, - }, - }, - Data: map[string][]byte{ - PreflightReportSecretKey: testReportWithOneEventData, - }, - }, - ), - namespace: "default", - appSlug: "test-app-slug", - event: testEvent, - }, - wantNumEvents: 2, - }, - { - name: "secret exists without data", - args: args{ - clientset: fake.NewSimpleClientset( - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf(PreflightReportSecretNameFormat, "test-app-slug"), - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "kotsadm", - UID: "test-uid", - }, - }, - }, - }, - ), - namespace: "default", - appSlug: "test-app-slug", - event: testEvent, - }, - wantNumEvents: 1, - }, - { - name: "secret exists with max number of events", - args: args{ - clientset: fake.NewSimpleClientset( - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kotsadm", - Namespace: "default", - UID: "test-uid", - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf(PreflightReportSecretNameFormat, "test-app-slug"), - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "kotsadm", - UID: "test-uid", - }, - }, - }, - Data: map[string][]byte{ - PreflightReportSecretKey: testReportWithMaxEventsData, - }, - }, - ), - namespace: "default", - appSlug: "test-app-slug", - event: testEvent, - }, - wantNumEvents: PreflightReportEventLimit, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req := require.New(t) - - err := CreatePreflightReportEvent(tt.args.clientset, tt.args.namespace, tt.args.appSlug, tt.args.event) - if tt.wantErr { - req.Error(err) - return - } - req.NoError(err) - - // validate secret exists and has the expected data - secret, err := tt.args.clientset.CoreV1().Secrets(tt.args.namespace).Get(context.TODO(), fmt.Sprintf(PreflightReportSecretNameFormat, tt.args.appSlug), metav1.GetOptions{}) - req.NoError(err) - req.NotNil(secret.Data[PreflightReportSecretKey]) - - report := &PreflightReport{} - err = DecodeAirgapReport(secret.Data[PreflightReportSecretKey], report) - req.NoError(err) - - req.Len(report.Events, tt.wantNumEvents) - - for _, event := range report.Events { - req.Equal(testEvent, event) - } - }) - } -} diff --git a/pkg/reporting/report.go b/pkg/reporting/report.go new file mode 100644 index 0000000000..56f5666171 --- /dev/null +++ b/pkg/reporting/report.go @@ -0,0 +1,251 @@ +package reporting + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "sync" + + "github.com/pkg/errors" + kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types" + "github.com/replicatedhq/kots/pkg/util" + corev1 "k8s.io/api/core/v1" + kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + ReportSecretNameFormat = "kotsadm-%s-report" + ReportSecretKey = "report" + ReportEventLimit = 4000 +) + +var ( + instanceReportMtx = sync.Mutex{} + preflightReportMtx = sync.Mutex{} +) + +type Report struct { + Events []ReportEvent `json:"events"` +} + +type ReportEvent interface { + GetReportSecretName(appSlug string) string + GetReportSecretKey() string + GetReportEventLimit() int + GetReportMtx() *sync.Mutex + GetReportType() string +} + +var _ ReportEvent = &InstanceReportEvent{} +var _ ReportEvent = &PreflightReportEvent{} + +type InstanceReport struct { + Events []InstanceReportEvent `json:"events"` +} + +type InstanceReportEvent struct { + ReportedAt int64 `json:"reported_at"` + LicenseID string `json:"license_id"` + InstanceID string `json:"instance_id"` + ClusterID string `json:"cluster_id"` + AppStatus string `json:"app_status"` + IsKurl bool `json:"is_kurl"` + KurlNodeCountTotal int `json:"kurl_node_count_total"` + KurlNodeCountReady int `json:"kurl_node_count_ready"` + K8sVersion string `json:"k8s_version"` + K8sDistribution string `json:"k8s_distribution,omitempty"` + KotsVersion string `json:"kots_version"` + KotsInstallID string `json:"kots_install_id,omitempty"` + KurlInstallID string `json:"kurl_install_id,omitempty"` + IsGitOpsEnabled bool `json:"is_gitops_enabled"` + GitOpsProvider string `json:"gitops_provider"` + DownstreamChannelID string `json:"downstream_channel_id,omitempty"` + DownstreamChannelSequence uint64 `json:"downstream_channel_sequence,omitempty"` + DownstreamChannelName string `json:"downstream_channel_name,omitempty"` + DownstreamSequence *int64 `json:"downstream_sequence,omitempty"` + DownstreamSource string `json:"downstream_source,omitempty"` + InstallStatus string `json:"install_status,omitempty"` + PreflightState string `json:"preflight_state,omitempty"` + SkipPreflights bool `json:"skip_preflights"` + ReplHelmInstalls int `json:"repl_helm_installs"` + NativeHelmInstalls int `json:"native_helm_installs"` +} + +func (i *InstanceReportEvent) GetReportType() string { + return "instance" +} + +func (i *InstanceReportEvent) GetReportSecretName(appSlug string) string { + return fmt.Sprintf(ReportSecretNameFormat, fmt.Sprintf("%s-%s", appSlug, i.GetReportType())) +} + +func (i *InstanceReportEvent) GetReportSecretKey() string { + return ReportSecretKey +} + +func (i *InstanceReportEvent) GetReportEventLimit() int { + return ReportEventLimit +} + +func (i *InstanceReportEvent) GetReportMtx() *sync.Mutex { + return &instanceReportMtx +} + +type PreflightReport struct { + Events []PreflightReportEvent `json:"events"` +} + +type PreflightReportEvent struct { + ReportedAt int64 `json:"reported_at"` + LicenseID string `json:"license_id"` + InstanceID string `json:"instance_id"` + ClusterID string `json:"cluster_id"` + Sequence int64 `json:"sequence"` + SkipPreflights bool `json:"skip_preflights"` + InstallStatus string `json:"install_status"` + IsCLI bool `json:"is_cli"` + PreflightStatus string `json:"preflight_status"` + AppStatus string `json:"app_status"` + KotsVersion string `json:"kots_version"` +} + +func (p *PreflightReportEvent) GetReportType() string { + return "preflight" +} + +func (p *PreflightReportEvent) GetReportSecretName(appSlug string) string { + return fmt.Sprintf(ReportSecretNameFormat, fmt.Sprintf("%s-%s", appSlug, p.GetReportType())) +} + +func (p *PreflightReportEvent) GetReportSecretKey() string { + return ReportSecretKey +} + +func (p *PreflightReportEvent) GetReportEventLimit() int { + return ReportEventLimit +} + +func (p *PreflightReportEvent) GetReportMtx() *sync.Mutex { + return &preflightReportMtx +} + +func CreateReportEvent(clientset kubernetes.Interface, namespace string, appSlug string, event ReportEvent) error { + event.GetReportMtx().Lock() + defer event.GetReportMtx().Unlock() + + existingSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), event.GetReportSecretName(appSlug), metav1.GetOptions{}) + if err != nil && !kuberneteserrors.IsNotFound(err) { + return errors.Wrap(err, "failed to get report secret") + } else if kuberneteserrors.IsNotFound(err) { + report := &Report{ + Events: []ReportEvent{event}, + } + data, err := EncodeReport(report) + if err != nil { + return errors.Wrap(err, "failed to encode report") + } + + secret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: event.GetReportSecretName(appSlug), + Namespace: namespace, + Labels: kotsadmtypes.GetKotsadmLabels(), + }, + Data: map[string][]byte{ + event.GetReportSecretKey(): data, + }, + } + + _, err = clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + return errors.Wrap(err, "failed to create report secret") + } + + return nil + } + + if existingSecret.Data == nil { + existingSecret.Data = map[string][]byte{} + } + + existingReport := &Report{} + if existingSecret.Data[event.GetReportSecretKey()] != nil { + if err := DecodeReport(existingSecret.Data[event.GetReportSecretKey()], existingReport, event.GetReportType()); err != nil { + return errors.Wrap(err, "failed to load existing report") + } + } + + existingReport.Events = append(existingReport.Events, event) + if len(existingReport.Events) > event.GetReportEventLimit() { + existingReport.Events = existingReport.Events[len(existingReport.Events)-event.GetReportEventLimit():] + } + + data, err := EncodeReport(existingReport) + if err != nil { + return errors.Wrap(err, "failed to encode existing report") + } + + existingSecret.Data[event.GetReportSecretKey()] = data + + _, err = clientset.CoreV1().Secrets(namespace).Update(context.TODO(), existingSecret, metav1.UpdateOptions{}) + if err != nil { + return errors.Wrap(err, "failed to update instance report secret") + } + + return nil +} + +func EncodeReport(r *Report) ([]byte, error) { + data, err := json.Marshal(r) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal report") + } + compressedData, err := util.GzipData(data) + if err != nil { + return nil, errors.Wrap(err, "failed to gzip report") + } + encodedData := base64.StdEncoding.EncodeToString(compressedData) + + return []byte(encodedData), nil +} + +func DecodeReport(encodedData []byte, existingReport *Report, reportType string) error { + decodedData, err := base64.StdEncoding.DecodeString(string(encodedData)) + if err != nil { + return errors.Wrap(err, "failed to decode report") + } + decompressedData, err := util.GunzipData(decodedData) + if err != nil { + return errors.Wrap(err, "failed to gunzip report") + } + + switch reportType { + case "instance": + r := &InstanceReport{} + if err := json.Unmarshal(decompressedData, r); err != nil { + return errors.Wrap(err, "failed to unmarshal report") + } + for _, event := range r.Events { + existingReport.Events = append(existingReport.Events, &event) + } + case "preflight": + r := &PreflightReport{} + if err := json.Unmarshal(decompressedData, r); err != nil { + return errors.Wrap(err, "failed to unmarshal report") + } + for _, event := range r.Events { + existingReport.Events = append(existingReport.Events, &event) + } + default: + return errors.Errorf("unknown report type %q", reportType) + } + + return nil +} diff --git a/pkg/reporting/report_test.go b/pkg/reporting/report_test.go new file mode 100644 index 0000000000..67ce42d54e --- /dev/null +++ b/pkg/reporting/report_test.go @@ -0,0 +1,274 @@ +package reporting + +import ( + "context" + "testing" + + kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" +) + +func Test_EncodeDecodeAirgapReport(t *testing.T) { + req := require.New(t) + + // instance report + testDownstreamSequence := int64(123) + testInstanceReport := &Report{ + Events: []ReportEvent{ + &InstanceReportEvent{ + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + AppStatus: "ready", + IsKurl: true, + KurlNodeCountTotal: 3, + KurlNodeCountReady: 3, + K8sVersion: "1.28.0", + K8sDistribution: "kurl", + KotsVersion: "1.100.0", + KotsInstallID: "test-kots-install-id", + KurlInstallID: "test-kurl-install-id", + IsGitOpsEnabled: true, + GitOpsProvider: "test-gitops-provider", + DownstreamChannelID: "test-downstream-channel-id", + DownstreamChannelSequence: 123, + DownstreamChannelName: "test-downstream-channel-name", + DownstreamSequence: &testDownstreamSequence, + DownstreamSource: "test-downstream-source", + InstallStatus: "installed", + PreflightState: "passed", + SkipPreflights: false, + ReplHelmInstalls: 1, + NativeHelmInstalls: 2, + }, + }, + } + + encodedInstanceReport, err := EncodeReport(testInstanceReport) + req.NoError(err) + + decodedInstanceReport := &Report{} + err = DecodeReport(encodedInstanceReport, decodedInstanceReport, "instance") + req.NoError(err) + + req.Equal(testInstanceReport, decodedInstanceReport) + + // preflight report + testPrelightReport := &Report{ + Events: []ReportEvent{ + &PreflightReportEvent{ + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + Sequence: 123, + SkipPreflights: false, + InstallStatus: "installed", + IsCLI: true, + PreflightStatus: "pass", + AppStatus: "ready", + KotsVersion: "1.100.0", + }, + }, + } + + encodedPreflightReport, err := EncodeReport(testPrelightReport) + req.NoError(err) + + decodedPreflightReport := &Report{} + err = DecodeReport(encodedPreflightReport, decodedPreflightReport, "preflight") + req.NoError(err) + + req.Equal(testPrelightReport, decodedPreflightReport) +} + +func Test_CreateReportEvent(t *testing.T) { + // instance report + testDownstreamSequence := int64(123) + testInstanceReportEvent := &InstanceReportEvent{ + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + AppStatus: "ready", + IsKurl: true, + KurlNodeCountTotal: 3, + KurlNodeCountReady: 3, + K8sVersion: "1.28.0", + K8sDistribution: "kurl", + KotsVersion: "1.100.0", + KotsInstallID: "test-kots-install-id", + KurlInstallID: "test-kurl-install-id", + IsGitOpsEnabled: true, + GitOpsProvider: "test-gitops-provider", + DownstreamChannelID: "test-downstream-channel-id", + DownstreamChannelSequence: 123, + DownstreamChannelName: "test-downstream-channel-name", + DownstreamSequence: &testDownstreamSequence, + DownstreamSource: "test-downstream-source", + InstallStatus: "installed", + PreflightState: "passed", + SkipPreflights: false, + ReplHelmInstalls: 1, + NativeHelmInstalls: 2, + } + + testPreflightReportEvent := &PreflightReportEvent{ + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + Sequence: 123, + SkipPreflights: false, + InstallStatus: "installed", + IsCLI: true, + PreflightStatus: "pass", + AppStatus: "ready", + KotsVersion: "1.100.0", + } + + tests := append(createTestsForEvent(t, testInstanceReportEvent), createTestsForEvent(t, testPreflightReportEvent)...) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := require.New(t) + + err := CreateReportEvent(tt.args.clientset, tt.args.namespace, tt.args.appSlug, tt.args.event) + if tt.wantErr { + req.Error(err) + return + } + req.NoError(err) + + // validate secret exists and has the expected data + secret, err := tt.args.clientset.CoreV1().Secrets(tt.args.namespace).Get(context.TODO(), tt.args.event.GetReportSecretName(tt.args.appSlug), metav1.GetOptions{}) + req.NoError(err) + req.NotNil(secret.Data[tt.args.event.GetReportSecretKey()]) + + report := &Report{} + err = DecodeReport(secret.Data[tt.args.event.GetReportSecretKey()], report, tt.args.event.GetReportType()) + req.NoError(err) + + req.Len(report.Events, tt.wantNumEvents) + + for _, event := range report.Events { + req.Equal(tt.args.event, event) + } + }) + } +} + +type CreateReportEventTest struct { + name string + args CreateReportEventTestArgs + wantNumEvents int + wantErr bool +} + +type CreateReportEventTestArgs struct { + clientset kubernetes.Interface + namespace string + appSlug string + event ReportEvent +} + +func createTestsForEvent(t *testing.T, testEvent ReportEvent) []CreateReportEventTest { + testReportWithOneEvent := &Report{ + Events: []ReportEvent{testEvent}, + } + testReportWithOneEventData, err := EncodeReport(testReportWithOneEvent) + require.NoError(t, err) + + // testReportWithMaxEvents := &Report{} + // for i := 0; i < testEvent.GetReportEventLimit(); i++ { + // testReportWithMaxEvents.Events = append(testReportWithMaxEvents.Events, testEvent) + // } + // testReportWithMaxEventsData, err := EncodeReport(testReportWithMaxEvents) + // require.NoError(t, err) + + type args struct { + clientset kubernetes.Interface + namespace string + appSlug string + event ReportEvent + } + tests := []CreateReportEventTest{ + { + name: "secret does not exist", + args: CreateReportEventTestArgs{ + clientset: fake.NewSimpleClientset(), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 1, + }, + { + name: "secret exists with an existing event", + args: CreateReportEventTestArgs{ + clientset: fake.NewSimpleClientset( + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testEvent.GetReportSecretName("test-app-slug"), + Namespace: "default", + Labels: kotsadmtypes.GetKotsadmLabels(), + }, + Data: map[string][]byte{ + testEvent.GetReportSecretKey(): testReportWithOneEventData, + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 2, + }, + { + name: "secret exists without data", + args: CreateReportEventTestArgs{ + clientset: fake.NewSimpleClientset( + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testEvent.GetReportSecretName("test-app-slug"), + Namespace: "default", + Labels: kotsadmtypes.GetKotsadmLabels(), + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + event: testEvent, + }, + wantNumEvents: 1, + }, + // { + // name: "secret exists with max number of events", + // args: CreateReportEventTestArgs{ + // clientset: fake.NewSimpleClientset( + // &corev1.Secret{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: testEvent.GetReportSecretName("test-app-slug"), + // Namespace: "default", + // Labels: kotsadmtypes.GetKotsadmLabels(), + // }, + // Data: map[string][]byte{ + // testEvent.GetReportSecretKey(): testReportWithMaxEventsData, + // }, + // }, + // ), + // namespace: "default", + // appSlug: "test-app-slug", + // event: testEvent, + // }, + // wantNumEvents: testEvent.GetReportEventLimit(), + // }, + } + + return tests +} diff --git a/pkg/reporting/types.go b/pkg/reporting/types.go index db3d7b46a2..95589b58ad 100644 --- a/pkg/reporting/types.go +++ b/pkg/reporting/types.go @@ -79,53 +79,3 @@ func (d Distribution) String() string { } return "unknown" } - -type InstanceReport struct { - Events []InstanceReportEvent `json:"events"` -} - -type InstanceReportEvent struct { - ReportedAt int64 `json:"reported_at"` - LicenseID string `json:"license_id"` - InstanceID string `json:"instance_id"` - ClusterID string `json:"cluster_id"` - AppStatus string `json:"app_status"` - IsKurl bool `json:"is_kurl"` - KurlNodeCountTotal int `json:"kurl_node_count_total"` - KurlNodeCountReady int `json:"kurl_node_count_ready"` - K8sVersion string `json:"k8s_version"` - K8sDistribution string `json:"k8s_distribution,omitempty"` - KotsVersion string `json:"kots_version"` - KotsInstallID string `json:"kots_install_id,omitempty"` - KurlInstallID string `json:"kurl_install_id,omitempty"` - IsGitOpsEnabled bool `json:"is_gitops_enabled"` - GitOpsProvider string `json:"gitops_provider"` - DownstreamChannelID string `json:"downstream_channel_id,omitempty"` - DownstreamChannelSequence uint64 `json:"downstream_channel_sequence,omitempty"` - DownstreamChannelName string `json:"downstream_channel_name,omitempty"` - DownstreamSequence *int64 `json:"downstream_sequence,omitempty"` - DownstreamSource string `json:"downstream_source,omitempty"` - InstallStatus string `json:"install_status,omitempty"` - PreflightState string `json:"preflight_state,omitempty"` - SkipPreflights bool `json:"skip_preflights"` - ReplHelmInstalls int `json:"repl_helm_installs"` - NativeHelmInstalls int `json:"native_helm_installs"` -} - -type PreflightReport struct { - Events []PreflightReportEvent `json:"events"` -} - -type PreflightReportEvent struct { - ReportedAt int64 `json:"reported_at"` - LicenseID string `json:"license_id"` - InstanceID string `json:"instance_id"` - ClusterID string `json:"cluster_id"` - Sequence int64 `json:"sequence"` - SkipPreflights bool `json:"skip_preflights"` - InstallStatus string `json:"install_status"` - IsCLI bool `json:"is_cli"` - PreflightStatus string `json:"preflight_status"` - AppStatus string `json:"app_status"` - KotsVersion string `json:"kots_version"` -} diff --git a/pkg/reporting/util.go b/pkg/reporting/util.go index 3a3f5c546f..e9da1ba89f 100644 --- a/pkg/reporting/util.go +++ b/pkg/reporting/util.go @@ -1,19 +1,12 @@ package reporting import ( - "encoding/base64" - "encoding/json" "net/http" "os" "regexp" "strconv" - "github.com/pkg/errors" "github.com/replicatedhq/kots/pkg/api/reporting/types" - "github.com/replicatedhq/kots/pkg/util" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - apimachinerytypes "k8s.io/apimachinery/pkg/types" ) func InjectReportingInfoHeaders(req *http.Request, reportingInfo *types.ReportingInfo) { @@ -94,62 +87,3 @@ func isDevEndpoint(endpoint string) bool { result, _ := regexp.MatchString(`replicated-app`, endpoint) return result } - -func AirgapReportSecret(name string, namespace string, kotsadmUID apimachinerytypes.UID, data []byte) *corev1.Secret { - return &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - // since this secret is created by the kotsadm deployment, we should set the owner reference - // so that it is deleted when the kotsadm deployment is deleted - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "kotsadm", - UID: kotsadmUID, - }, - }, - }, - Data: map[string][]byte{ - InstanceReportSecretKey: data, - }, - } -} - -// EncodeAirgapReport marshals, compresses, and base64 encodes the given report -func EncodeAirgapReport(r any) ([]byte, error) { - data, err := json.Marshal(r) - if err != nil { - return nil, errors.Wrap(err, "failed to marshal airgap report") - } - compressedData, err := util.GzipData(data) - if err != nil { - return nil, errors.Wrap(err, "failed to gzip airgap report") - } - encodedData := base64.StdEncoding.EncodeToString(compressedData) - - return []byte(encodedData), nil -} - -// DecodeAirgapReport base64 decodes, uncompresses, and unmarshals the given report -func DecodeAirgapReport(encodedData []byte, r any) error { - decodedData, err := base64.StdEncoding.DecodeString(string(encodedData)) - if err != nil { - return errors.Wrap(err, "failed to decode airgap report") - } - decompressedData, err := util.GunzipData(decodedData) - if err != nil { - return errors.Wrap(err, "failed to gunzip airgap report") - } - - if err := json.Unmarshal(decompressedData, r); err != nil { - return errors.Wrap(err, "failed to unmarshal airgap report") - } - - return nil -} diff --git a/pkg/reporting/util_test.go b/pkg/reporting/util_test.go deleted file mode 100644 index fe321f2581..0000000000 --- a/pkg/reporting/util_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package reporting - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func Test_EncodeDecodeAirgapReport(t *testing.T) { - req := require.New(t) - - // instance report - testDownstreamSequence := int64(123) - testInstanceReport := &InstanceReport{ - Events: []InstanceReportEvent{ - { - ReportedAt: 1234567890, - LicenseID: "test-license-id", - InstanceID: "test-instance-id", - ClusterID: "test-cluster-id", - AppStatus: "ready", - IsKurl: true, - KurlNodeCountTotal: 3, - KurlNodeCountReady: 3, - K8sVersion: "1.28.0", - K8sDistribution: "kurl", - KotsVersion: "1.100.0", - KotsInstallID: "test-kots-install-id", - KurlInstallID: "test-kurl-install-id", - IsGitOpsEnabled: true, - GitOpsProvider: "test-gitops-provider", - DownstreamChannelID: "test-downstream-channel-id", - DownstreamChannelSequence: 123, - DownstreamChannelName: "test-downstream-channel-name", - DownstreamSequence: &testDownstreamSequence, - DownstreamSource: "test-downstream-source", - InstallStatus: "installed", - PreflightState: "passed", - SkipPreflights: false, - ReplHelmInstalls: 1, - NativeHelmInstalls: 2, - }, - }, - } - - encodedInstanceReport, err := EncodeAirgapReport(testInstanceReport) - req.NoError(err) - - decodedInstanceReport := &InstanceReport{} - err = DecodeAirgapReport(encodedInstanceReport, decodedInstanceReport) - req.NoError(err) - - req.Equal(testInstanceReport, decodedInstanceReport) - - // preflight report - testPrelightReport := &PreflightReport{ - Events: []PreflightReportEvent{ - { - ReportedAt: 1234567890, - LicenseID: "test-license-id", - InstanceID: "test-instance-id", - ClusterID: "test-cluster-id", - Sequence: 123, - SkipPreflights: false, - InstallStatus: "installed", - IsCLI: true, - PreflightStatus: "pass", - AppStatus: "ready", - KotsVersion: "1.100.0", - }, - }, - } - - encodedPreflightReport, err := EncodeAirgapReport(testPrelightReport) - req.NoError(err) - - decodedPreflightReport := &PreflightReport{} - err = DecodeAirgapReport(encodedPreflightReport, decodedPreflightReport) - req.NoError(err) - - req.Equal(testPrelightReport, decodedPreflightReport) -} From dc017d60a65b75bb841b1c40afc0ea7e27113b13 Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Wed, 25 Oct 2023 15:41:58 +0000 Subject: [PATCH 3/6] refactor interface to report level --- pkg/reporting/app_airgap.go | 64 +++++----- pkg/reporting/instance_report.go | 76 ++++++++++++ pkg/reporting/preflight_airgap.go | 32 ++--- pkg/reporting/preflight_report.go | 62 ++++++++++ pkg/reporting/report.go | 187 +++++++--------------------- pkg/reporting/report_test.go | 196 +++++++++++++++--------------- 6 files changed, 333 insertions(+), 284 deletions(-) create mode 100644 pkg/reporting/instance_report.go create mode 100644 pkg/reporting/preflight_report.go diff --git a/pkg/reporting/app_airgap.go b/pkg/reporting/app_airgap.go index a7c4307ffd..216935453a 100644 --- a/pkg/reporting/app_airgap.go +++ b/pkg/reporting/app_airgap.go @@ -25,47 +25,51 @@ func (r *AirgapReporter) SubmitAppInfo(appID string) error { } reportingInfo := GetReportingInfo(appID) - event := GetInstanceReportEvent(license.Spec.LicenseID, reportingInfo) + report := GetInstanceReport(license.Spec.LicenseID, reportingInfo) - if err := CreateReportEvent(r.clientset, util.PodNamespace, a.Slug, event); err != nil { - return errors.Wrap(err, "failed to create instance report event") + if err := AppendReport(r.clientset, util.PodNamespace, a.Slug, report); err != nil { + return errors.Wrap(err, "failed to append instance report") } return nil } -func GetInstanceReportEvent(licenseID string, reportingInfo *types.ReportingInfo) *InstanceReportEvent { +func GetInstanceReport(licenseID string, reportingInfo *types.ReportingInfo) *InstanceReport { // not using the "cursor" packages because it doesn't provide access to the underlying int64 downstreamSequence, err := strconv.ParseUint(reportingInfo.Downstream.Cursor, 10, 64) if err != nil { logger.Debugf("failed to parse downstream cursor %q: %v", reportingInfo.Downstream.Cursor, err) } - return &InstanceReportEvent{ - ReportedAt: time.Now().UTC().UnixMilli(), - LicenseID: licenseID, - InstanceID: reportingInfo.InstanceID, - ClusterID: reportingInfo.ClusterID, - AppStatus: reportingInfo.AppStatus, - IsKurl: reportingInfo.IsKurl, - KurlNodeCountTotal: reportingInfo.KurlNodeCountTotal, - KurlNodeCountReady: reportingInfo.KurlNodeCountReady, - K8sVersion: reportingInfo.K8sVersion, - K8sDistribution: reportingInfo.K8sDistribution, - KotsVersion: reportingInfo.KOTSVersion, - KotsInstallID: reportingInfo.KOTSInstallID, - KurlInstallID: reportingInfo.KURLInstallID, - IsGitOpsEnabled: reportingInfo.IsGitOpsEnabled, - GitOpsProvider: reportingInfo.GitOpsProvider, - DownstreamChannelID: reportingInfo.Downstream.ChannelID, - DownstreamChannelSequence: downstreamSequence, - DownstreamChannelName: reportingInfo.Downstream.ChannelName, - DownstreamSequence: reportingInfo.Downstream.Sequence, - DownstreamSource: reportingInfo.Downstream.Source, - InstallStatus: reportingInfo.Downstream.Status, - PreflightState: reportingInfo.Downstream.PreflightState, - SkipPreflights: reportingInfo.Downstream.SkipPreflights, - ReplHelmInstalls: reportingInfo.Downstream.ReplHelmInstalls, - NativeHelmInstalls: reportingInfo.Downstream.NativeHelmInstalls, + return &InstanceReport{ + Events: []InstanceReportEvent{ + { + ReportedAt: time.Now().UTC().UnixMilli(), + LicenseID: licenseID, + InstanceID: reportingInfo.InstanceID, + ClusterID: reportingInfo.ClusterID, + AppStatus: reportingInfo.AppStatus, + IsKurl: reportingInfo.IsKurl, + KurlNodeCountTotal: reportingInfo.KurlNodeCountTotal, + KurlNodeCountReady: reportingInfo.KurlNodeCountReady, + K8sVersion: reportingInfo.K8sVersion, + K8sDistribution: reportingInfo.K8sDistribution, + KotsVersion: reportingInfo.KOTSVersion, + KotsInstallID: reportingInfo.KOTSInstallID, + KurlInstallID: reportingInfo.KURLInstallID, + IsGitOpsEnabled: reportingInfo.IsGitOpsEnabled, + GitOpsProvider: reportingInfo.GitOpsProvider, + DownstreamChannelID: reportingInfo.Downstream.ChannelID, + DownstreamChannelSequence: downstreamSequence, + DownstreamChannelName: reportingInfo.Downstream.ChannelName, + DownstreamSequence: reportingInfo.Downstream.Sequence, + DownstreamSource: reportingInfo.Downstream.Source, + InstallStatus: reportingInfo.Downstream.Status, + PreflightState: reportingInfo.Downstream.PreflightState, + SkipPreflights: reportingInfo.Downstream.SkipPreflights, + ReplHelmInstalls: reportingInfo.Downstream.ReplHelmInstalls, + NativeHelmInstalls: reportingInfo.Downstream.NativeHelmInstalls, + }, + }, } } diff --git a/pkg/reporting/instance_report.go b/pkg/reporting/instance_report.go new file mode 100644 index 0000000000..f1271d1ae3 --- /dev/null +++ b/pkg/reporting/instance_report.go @@ -0,0 +1,76 @@ +package reporting + +import ( + "fmt" + "sync" + + "github.com/pkg/errors" +) + +var instanceReportMtx = sync.Mutex{} + +type InstanceReport struct { + Events []InstanceReportEvent `json:"events"` +} + +type InstanceReportEvent struct { + ReportedAt int64 `json:"reported_at"` + LicenseID string `json:"license_id"` + InstanceID string `json:"instance_id"` + ClusterID string `json:"cluster_id"` + AppStatus string `json:"app_status"` + IsKurl bool `json:"is_kurl"` + KurlNodeCountTotal int `json:"kurl_node_count_total"` + KurlNodeCountReady int `json:"kurl_node_count_ready"` + K8sVersion string `json:"k8s_version"` + K8sDistribution string `json:"k8s_distribution,omitempty"` + KotsVersion string `json:"kots_version"` + KotsInstallID string `json:"kots_install_id,omitempty"` + KurlInstallID string `json:"kurl_install_id,omitempty"` + IsGitOpsEnabled bool `json:"is_gitops_enabled"` + GitOpsProvider string `json:"gitops_provider"` + DownstreamChannelID string `json:"downstream_channel_id,omitempty"` + DownstreamChannelSequence uint64 `json:"downstream_channel_sequence,omitempty"` + DownstreamChannelName string `json:"downstream_channel_name,omitempty"` + DownstreamSequence *int64 `json:"downstream_sequence,omitempty"` + DownstreamSource string `json:"downstream_source,omitempty"` + InstallStatus string `json:"install_status,omitempty"` + PreflightState string `json:"preflight_state,omitempty"` + SkipPreflights bool `json:"skip_preflights"` + ReplHelmInstalls int `json:"repl_helm_installs"` + NativeHelmInstalls int `json:"native_helm_installs"` +} + +func (r *InstanceReport) GetType() ReportType { + return ReportTypeInstance +} + +func (r *InstanceReport) GetSecretName(appSlug string) string { + return fmt.Sprintf(ReportSecretNameFormat, fmt.Sprintf("%s-%s", appSlug, r.GetType())) +} + +func (r *InstanceReport) GetSecretKey() string { + return ReportSecretKey +} + +func (r *InstanceReport) AppendEvents(report Report) error { + reportToAppend, ok := report.(*InstanceReport) + if !ok { + return errors.Errorf("report is not an instance report") + } + + r.Events = append(r.Events, reportToAppend.Events...) + if len(r.Events) > r.GetEventLimit() { + r.Events = r.Events[len(r.Events)-r.GetEventLimit():] + } + + return nil +} + +func (r *InstanceReport) GetEventLimit() int { + return ReportEventLimit +} + +func (r *InstanceReport) GetMtx() *sync.Mutex { + return &instanceReportMtx +} diff --git a/pkg/reporting/preflight_airgap.go b/pkg/reporting/preflight_airgap.go index daf97433ac..a6018d8823 100644 --- a/pkg/reporting/preflight_airgap.go +++ b/pkg/reporting/preflight_airgap.go @@ -19,22 +19,26 @@ func (r *AirgapReporter) SubmitPreflightData(license *kotsv1beta1.License, appID return errors.Wrap(err, "failed to get airgapped app") } - event := &PreflightReportEvent{ - ReportedAt: time.Now().UTC().UnixMilli(), - LicenseID: license.Spec.LicenseID, - InstanceID: appID, - ClusterID: clusterID, - Sequence: sequence, - SkipPreflights: skipPreflights, - InstallStatus: string(installStatus), - IsCLI: isCLI, - PreflightStatus: preflightStatus, - AppStatus: appStatus, - KotsVersion: buildversion.Version(), + report := &PreflightReport{ + Events: []PreflightReportEvent{ + { + ReportedAt: time.Now().UTC().UnixMilli(), + LicenseID: license.Spec.LicenseID, + InstanceID: appID, + ClusterID: clusterID, + Sequence: sequence, + SkipPreflights: skipPreflights, + InstallStatus: string(installStatus), + IsCLI: isCLI, + PreflightStatus: preflightStatus, + AppStatus: appStatus, + KotsVersion: buildversion.Version(), + }, + }, } - if err := CreateReportEvent(r.clientset, util.PodNamespace, app.Slug, event); err != nil { - return errors.Wrap(err, "failed to create preflight report event") + if err := AppendReport(r.clientset, util.PodNamespace, app.Slug, report); err != nil { + return errors.Wrap(err, "failed to append preflight report") } return nil diff --git a/pkg/reporting/preflight_report.go b/pkg/reporting/preflight_report.go new file mode 100644 index 0000000000..9b4a521d9c --- /dev/null +++ b/pkg/reporting/preflight_report.go @@ -0,0 +1,62 @@ +package reporting + +import ( + "fmt" + "sync" + + "github.com/pkg/errors" +) + +var preflightReportMtx = sync.Mutex{} + +type PreflightReport struct { + Events []PreflightReportEvent `json:"events"` +} + +type PreflightReportEvent struct { + ReportedAt int64 `json:"reported_at"` + LicenseID string `json:"license_id"` + InstanceID string `json:"instance_id"` + ClusterID string `json:"cluster_id"` + Sequence int64 `json:"sequence"` + SkipPreflights bool `json:"skip_preflights"` + InstallStatus string `json:"install_status"` + IsCLI bool `json:"is_cli"` + PreflightStatus string `json:"preflight_status"` + AppStatus string `json:"app_status"` + KotsVersion string `json:"kots_version"` +} + +func (r *PreflightReport) GetType() ReportType { + return ReportTypePreflight +} + +func (r *PreflightReport) GetSecretName(appSlug string) string { + return fmt.Sprintf(ReportSecretNameFormat, fmt.Sprintf("%s-%s", appSlug, r.GetType())) +} + +func (r *PreflightReport) GetSecretKey() string { + return ReportSecretKey +} + +func (r *PreflightReport) AppendEvents(report Report) error { + reportToAppend, ok := report.(*PreflightReport) + if !ok { + return errors.Errorf("report is not a preflight report") + } + + r.Events = append(r.Events, reportToAppend.Events...) + if len(r.Events) > r.GetEventLimit() { + r.Events = r.Events[len(r.Events)-r.GetEventLimit():] + } + + return nil +} + +func (r *PreflightReport) GetEventLimit() int { + return ReportEventLimit +} + +func (r *PreflightReport) GetMtx() *sync.Mutex { + return &preflightReportMtx +} diff --git a/pkg/reporting/report.go b/pkg/reporting/report.go index 56f5666171..020969e372 100644 --- a/pkg/reporting/report.go +++ b/pkg/reporting/report.go @@ -4,7 +4,6 @@ import ( "context" "encoding/base64" "encoding/json" - "fmt" "sync" "github.com/pkg/errors" @@ -22,127 +21,35 @@ const ( ReportEventLimit = 4000 ) -var ( - instanceReportMtx = sync.Mutex{} - preflightReportMtx = sync.Mutex{} -) - -type Report struct { - Events []ReportEvent `json:"events"` -} - -type ReportEvent interface { - GetReportSecretName(appSlug string) string - GetReportSecretKey() string - GetReportEventLimit() int - GetReportMtx() *sync.Mutex - GetReportType() string -} - -var _ ReportEvent = &InstanceReportEvent{} -var _ ReportEvent = &PreflightReportEvent{} - -type InstanceReport struct { - Events []InstanceReportEvent `json:"events"` -} - -type InstanceReportEvent struct { - ReportedAt int64 `json:"reported_at"` - LicenseID string `json:"license_id"` - InstanceID string `json:"instance_id"` - ClusterID string `json:"cluster_id"` - AppStatus string `json:"app_status"` - IsKurl bool `json:"is_kurl"` - KurlNodeCountTotal int `json:"kurl_node_count_total"` - KurlNodeCountReady int `json:"kurl_node_count_ready"` - K8sVersion string `json:"k8s_version"` - K8sDistribution string `json:"k8s_distribution,omitempty"` - KotsVersion string `json:"kots_version"` - KotsInstallID string `json:"kots_install_id,omitempty"` - KurlInstallID string `json:"kurl_install_id,omitempty"` - IsGitOpsEnabled bool `json:"is_gitops_enabled"` - GitOpsProvider string `json:"gitops_provider"` - DownstreamChannelID string `json:"downstream_channel_id,omitempty"` - DownstreamChannelSequence uint64 `json:"downstream_channel_sequence,omitempty"` - DownstreamChannelName string `json:"downstream_channel_name,omitempty"` - DownstreamSequence *int64 `json:"downstream_sequence,omitempty"` - DownstreamSource string `json:"downstream_source,omitempty"` - InstallStatus string `json:"install_status,omitempty"` - PreflightState string `json:"preflight_state,omitempty"` - SkipPreflights bool `json:"skip_preflights"` - ReplHelmInstalls int `json:"repl_helm_installs"` - NativeHelmInstalls int `json:"native_helm_installs"` -} - -func (i *InstanceReportEvent) GetReportType() string { - return "instance" -} - -func (i *InstanceReportEvent) GetReportSecretName(appSlug string) string { - return fmt.Sprintf(ReportSecretNameFormat, fmt.Sprintf("%s-%s", appSlug, i.GetReportType())) -} - -func (i *InstanceReportEvent) GetReportSecretKey() string { - return ReportSecretKey -} - -func (i *InstanceReportEvent) GetReportEventLimit() int { - return ReportEventLimit -} - -func (i *InstanceReportEvent) GetReportMtx() *sync.Mutex { - return &instanceReportMtx -} - -type PreflightReport struct { - Events []PreflightReportEvent `json:"events"` -} +type ReportType string -type PreflightReportEvent struct { - ReportedAt int64 `json:"reported_at"` - LicenseID string `json:"license_id"` - InstanceID string `json:"instance_id"` - ClusterID string `json:"cluster_id"` - Sequence int64 `json:"sequence"` - SkipPreflights bool `json:"skip_preflights"` - InstallStatus string `json:"install_status"` - IsCLI bool `json:"is_cli"` - PreflightStatus string `json:"preflight_status"` - AppStatus string `json:"app_status"` - KotsVersion string `json:"kots_version"` -} +const ( + ReportTypeInstance ReportType = "instance" + ReportTypePreflight ReportType = "preflight" +) -func (p *PreflightReportEvent) GetReportType() string { - return "preflight" +type Report interface { + GetType() ReportType + GetSecretName(appSlug string) string + GetSecretKey() string + AppendEvents(report Report) error + GetEventLimit() int + GetMtx() *sync.Mutex } -func (p *PreflightReportEvent) GetReportSecretName(appSlug string) string { - return fmt.Sprintf(ReportSecretNameFormat, fmt.Sprintf("%s-%s", appSlug, p.GetReportType())) -} +type ReportEvent interface{} -func (p *PreflightReportEvent) GetReportSecretKey() string { - return ReportSecretKey -} +var _ Report = &InstanceReport{} +var _ Report = &PreflightReport{} -func (p *PreflightReportEvent) GetReportEventLimit() int { - return ReportEventLimit -} +func AppendReport(clientset kubernetes.Interface, namespace string, appSlug string, report Report) error { + report.GetMtx().Lock() + defer report.GetMtx().Unlock() -func (p *PreflightReportEvent) GetReportMtx() *sync.Mutex { - return &preflightReportMtx -} - -func CreateReportEvent(clientset kubernetes.Interface, namespace string, appSlug string, event ReportEvent) error { - event.GetReportMtx().Lock() - defer event.GetReportMtx().Unlock() - - existingSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), event.GetReportSecretName(appSlug), metav1.GetOptions{}) + existingSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), report.GetSecretName(appSlug), metav1.GetOptions{}) if err != nil && !kuberneteserrors.IsNotFound(err) { return errors.Wrap(err, "failed to get report secret") } else if kuberneteserrors.IsNotFound(err) { - report := &Report{ - Events: []ReportEvent{event}, - } data, err := EncodeReport(report) if err != nil { return errors.Wrap(err, "failed to encode report") @@ -154,12 +61,12 @@ func CreateReportEvent(clientset kubernetes.Interface, namespace string, appSlug Kind: "Secret", }, ObjectMeta: metav1.ObjectMeta{ - Name: event.GetReportSecretName(appSlug), + Name: report.GetSecretName(appSlug), Namespace: namespace, Labels: kotsadmtypes.GetKotsadmLabels(), }, Data: map[string][]byte{ - event.GetReportSecretKey(): data, + report.GetSecretKey(): data, }, } @@ -175,16 +82,19 @@ func CreateReportEvent(clientset kubernetes.Interface, namespace string, appSlug existingSecret.Data = map[string][]byte{} } - existingReport := &Report{} - if existingSecret.Data[event.GetReportSecretKey()] != nil { - if err := DecodeReport(existingSecret.Data[event.GetReportSecretKey()], existingReport, event.GetReportType()); err != nil { + var existingReport Report + if existingSecret.Data[report.GetSecretKey()] != nil { + existingReport, err = DecodeReport(existingSecret.Data[report.GetSecretKey()], report.GetType()) + if err != nil { return errors.Wrap(err, "failed to load existing report") } - } - existingReport.Events = append(existingReport.Events, event) - if len(existingReport.Events) > event.GetReportEventLimit() { - existingReport.Events = existingReport.Events[len(existingReport.Events)-event.GetReportEventLimit():] + if err := existingReport.AppendEvents(report); err != nil { + return errors.Wrap(err, "failed to append events to existing report") + } + } else { + // secret exists but doesn't have the report key, so just use the report that was passed in + existingReport = report } data, err := EncodeReport(existingReport) @@ -192,17 +102,17 @@ func CreateReportEvent(clientset kubernetes.Interface, namespace string, appSlug return errors.Wrap(err, "failed to encode existing report") } - existingSecret.Data[event.GetReportSecretKey()] = data + existingSecret.Data[report.GetSecretKey()] = data _, err = clientset.CoreV1().Secrets(namespace).Update(context.TODO(), existingSecret, metav1.UpdateOptions{}) if err != nil { - return errors.Wrap(err, "failed to update instance report secret") + return errors.Wrap(err, "failed to update report secret") } return nil } -func EncodeReport(r *Report) ([]byte, error) { +func EncodeReport(r Report) ([]byte, error) { data, err := json.Marshal(r) if err != nil { return nil, errors.Wrap(err, "failed to marshal report") @@ -216,36 +126,31 @@ func EncodeReport(r *Report) ([]byte, error) { return []byte(encodedData), nil } -func DecodeReport(encodedData []byte, existingReport *Report, reportType string) error { +func DecodeReport(encodedData []byte, reportType ReportType) (Report, error) { decodedData, err := base64.StdEncoding.DecodeString(string(encodedData)) if err != nil { - return errors.Wrap(err, "failed to decode report") + return nil, errors.Wrap(err, "failed to decode report") } decompressedData, err := util.GunzipData(decodedData) if err != nil { - return errors.Wrap(err, "failed to gunzip report") + return nil, errors.Wrap(err, "failed to gunzip report") } + var r Report switch reportType { - case "instance": - r := &InstanceReport{} + case ReportTypeInstance: + r = &InstanceReport{} if err := json.Unmarshal(decompressedData, r); err != nil { - return errors.Wrap(err, "failed to unmarshal report") - } - for _, event := range r.Events { - existingReport.Events = append(existingReport.Events, &event) + return nil, errors.Wrap(err, "failed to unmarshal instance report") } - case "preflight": - r := &PreflightReport{} + case ReportTypePreflight: + r = &PreflightReport{} if err := json.Unmarshal(decompressedData, r); err != nil { - return errors.Wrap(err, "failed to unmarshal report") - } - for _, event := range r.Events { - existingReport.Events = append(existingReport.Events, &event) + return nil, errors.Wrap(err, "failed to unmarshal preflight report") } default: - return errors.Errorf("unknown report type %q", reportType) + return nil, errors.Errorf("unknown report type %q", reportType) } - return nil + return r, nil } diff --git a/pkg/reporting/report_test.go b/pkg/reporting/report_test.go index 67ce42d54e..b6c46d3bc8 100644 --- a/pkg/reporting/report_test.go +++ b/pkg/reporting/report_test.go @@ -17,9 +17,9 @@ func Test_EncodeDecodeAirgapReport(t *testing.T) { // instance report testDownstreamSequence := int64(123) - testInstanceReport := &Report{ - Events: []ReportEvent{ - &InstanceReportEvent{ + testInstanceReport := &InstanceReport{ + Events: []InstanceReportEvent{ + { ReportedAt: 1234567890, LicenseID: "test-license-id", InstanceID: "test-instance-id", @@ -52,16 +52,15 @@ func Test_EncodeDecodeAirgapReport(t *testing.T) { encodedInstanceReport, err := EncodeReport(testInstanceReport) req.NoError(err) - decodedInstanceReport := &Report{} - err = DecodeReport(encodedInstanceReport, decodedInstanceReport, "instance") + decodedInstanceReport, err := DecodeReport(encodedInstanceReport, testInstanceReport.GetType()) req.NoError(err) req.Equal(testInstanceReport, decodedInstanceReport) // preflight report - testPrelightReport := &Report{ - Events: []ReportEvent{ - &PreflightReportEvent{ + testPrelightReport := &PreflightReport{ + Events: []PreflightReportEvent{ + { ReportedAt: 1234567890, LicenseID: "test-license-id", InstanceID: "test-instance-id", @@ -80,8 +79,7 @@ func Test_EncodeDecodeAirgapReport(t *testing.T) { encodedPreflightReport, err := EncodeReport(testPrelightReport) req.NoError(err) - decodedPreflightReport := &Report{} - err = DecodeReport(encodedPreflightReport, decodedPreflightReport, "preflight") + decodedPreflightReport, err := DecodeReport(encodedPreflightReport, testPrelightReport.GetType()) req.NoError(err) req.Equal(testPrelightReport, decodedPreflightReport) @@ -90,55 +88,64 @@ func Test_EncodeDecodeAirgapReport(t *testing.T) { func Test_CreateReportEvent(t *testing.T) { // instance report testDownstreamSequence := int64(123) - testInstanceReportEvent := &InstanceReportEvent{ - ReportedAt: 1234567890, - LicenseID: "test-license-id", - InstanceID: "test-instance-id", - ClusterID: "test-cluster-id", - AppStatus: "ready", - IsKurl: true, - KurlNodeCountTotal: 3, - KurlNodeCountReady: 3, - K8sVersion: "1.28.0", - K8sDistribution: "kurl", - KotsVersion: "1.100.0", - KotsInstallID: "test-kots-install-id", - KurlInstallID: "test-kurl-install-id", - IsGitOpsEnabled: true, - GitOpsProvider: "test-gitops-provider", - DownstreamChannelID: "test-downstream-channel-id", - DownstreamChannelSequence: 123, - DownstreamChannelName: "test-downstream-channel-name", - DownstreamSequence: &testDownstreamSequence, - DownstreamSource: "test-downstream-source", - InstallStatus: "installed", - PreflightState: "passed", - SkipPreflights: false, - ReplHelmInstalls: 1, - NativeHelmInstalls: 2, + testInstanceReport := &InstanceReport{ + Events: []InstanceReportEvent{ + { + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + AppStatus: "ready", + IsKurl: true, + KurlNodeCountTotal: 3, + KurlNodeCountReady: 3, + K8sVersion: "1.28.0", + K8sDistribution: "kurl", + KotsVersion: "1.100.0", + KotsInstallID: "test-kots-install-id", + KurlInstallID: "test-kurl-install-id", + IsGitOpsEnabled: true, + GitOpsProvider: "test-gitops-provider", + DownstreamChannelID: "test-downstream-channel-id", + DownstreamChannelSequence: 123, + DownstreamChannelName: "test-downstream-channel-name", + DownstreamSequence: &testDownstreamSequence, + DownstreamSource: "test-downstream-source", + InstallStatus: "installed", + PreflightState: "passed", + SkipPreflights: false, + ReplHelmInstalls: 1, + NativeHelmInstalls: 2, + }, + }, } - testPreflightReportEvent := &PreflightReportEvent{ - ReportedAt: 1234567890, - LicenseID: "test-license-id", - InstanceID: "test-instance-id", - ClusterID: "test-cluster-id", - Sequence: 123, - SkipPreflights: false, - InstallStatus: "installed", - IsCLI: true, - PreflightStatus: "pass", - AppStatus: "ready", - KotsVersion: "1.100.0", + // preflight report + testPreflightReport := &PreflightReport{ + Events: []PreflightReportEvent{ + { + ReportedAt: 1234567890, + LicenseID: "test-license-id", + InstanceID: "test-instance-id", + ClusterID: "test-cluster-id", + Sequence: 123, + SkipPreflights: false, + InstallStatus: "installed", + IsCLI: true, + PreflightStatus: "pass", + AppStatus: "ready", + KotsVersion: "1.100.0", + }, + }, } - tests := append(createTestsForEvent(t, testInstanceReportEvent), createTestsForEvent(t, testPreflightReportEvent)...) + tests := append(createTestsForEvent(t, testInstanceReport), createTestsForEvent(t, testPreflightReport)...) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := require.New(t) - err := CreateReportEvent(tt.args.clientset, tt.args.namespace, tt.args.appSlug, tt.args.event) + err := AppendReport(tt.args.clientset, tt.args.namespace, tt.args.appSlug, tt.args.report) if tt.wantErr { req.Error(err) return @@ -146,19 +153,13 @@ func Test_CreateReportEvent(t *testing.T) { req.NoError(err) // validate secret exists and has the expected data - secret, err := tt.args.clientset.CoreV1().Secrets(tt.args.namespace).Get(context.TODO(), tt.args.event.GetReportSecretName(tt.args.appSlug), metav1.GetOptions{}) + secret, err := tt.args.clientset.CoreV1().Secrets(tt.args.namespace).Get(context.TODO(), tt.args.report.GetSecretName(tt.args.appSlug), metav1.GetOptions{}) req.NoError(err) - req.NotNil(secret.Data[tt.args.event.GetReportSecretKey()]) + req.NotNil(secret.Data[tt.args.report.GetSecretKey()]) - report := &Report{} - err = DecodeReport(secret.Data[tt.args.event.GetReportSecretKey()], report, tt.args.event.GetReportType()) + report, err := DecodeReport(secret.Data[tt.args.report.GetSecretKey()], tt.args.report.GetType()) req.NoError(err) - - req.Len(report.Events, tt.wantNumEvents) - - for _, event := range report.Events { - req.Equal(tt.args.event, event) - } + req.Equal(tt.args.report, report) }) } } @@ -174,22 +175,19 @@ type CreateReportEventTestArgs struct { clientset kubernetes.Interface namespace string appSlug string - event ReportEvent + report Report } -func createTestsForEvent(t *testing.T, testEvent ReportEvent) []CreateReportEventTest { - testReportWithOneEvent := &Report{ - Events: []ReportEvent{testEvent}, - } - testReportWithOneEventData, err := EncodeReport(testReportWithOneEvent) +func createTestsForEvent(t *testing.T, testReport Report) []CreateReportEventTest { + testReportWithOneEventData, err := EncodeReport(testReport) require.NoError(t, err) - // testReportWithMaxEvents := &Report{} - // for i := 0; i < testEvent.GetReportEventLimit(); i++ { - // testReportWithMaxEvents.Events = append(testReportWithMaxEvents.Events, testEvent) - // } - // testReportWithMaxEventsData, err := EncodeReport(testReportWithMaxEvents) - // require.NoError(t, err) + for i := 0; i < testReport.GetEventLimit(); i++ { + err := testReport.AppendEvents(testReport) + require.NoError(t, err) + } + testReportWithMaxEventsData, err := EncodeReport(testReport) + require.NoError(t, err) type args struct { clientset kubernetes.Interface @@ -204,7 +202,7 @@ func createTestsForEvent(t *testing.T, testEvent ReportEvent) []CreateReportEven clientset: fake.NewSimpleClientset(), namespace: "default", appSlug: "test-app-slug", - event: testEvent, + report: testReport, }, wantNumEvents: 1, }, @@ -214,18 +212,18 @@ func createTestsForEvent(t *testing.T, testEvent ReportEvent) []CreateReportEven clientset: fake.NewSimpleClientset( &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: testEvent.GetReportSecretName("test-app-slug"), + Name: testReport.GetSecretName("test-app-slug"), Namespace: "default", Labels: kotsadmtypes.GetKotsadmLabels(), }, Data: map[string][]byte{ - testEvent.GetReportSecretKey(): testReportWithOneEventData, + testReport.GetSecretKey(): testReportWithOneEventData, }, }, ), namespace: "default", appSlug: "test-app-slug", - event: testEvent, + report: testReport, }, wantNumEvents: 2, }, @@ -235,7 +233,7 @@ func createTestsForEvent(t *testing.T, testEvent ReportEvent) []CreateReportEven clientset: fake.NewSimpleClientset( &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: testEvent.GetReportSecretName("test-app-slug"), + Name: testReport.GetSecretName("test-app-slug"), Namespace: "default", Labels: kotsadmtypes.GetKotsadmLabels(), }, @@ -243,31 +241,31 @@ func createTestsForEvent(t *testing.T, testEvent ReportEvent) []CreateReportEven ), namespace: "default", appSlug: "test-app-slug", - event: testEvent, + report: testReport, }, wantNumEvents: 1, }, - // { - // name: "secret exists with max number of events", - // args: CreateReportEventTestArgs{ - // clientset: fake.NewSimpleClientset( - // &corev1.Secret{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: testEvent.GetReportSecretName("test-app-slug"), - // Namespace: "default", - // Labels: kotsadmtypes.GetKotsadmLabels(), - // }, - // Data: map[string][]byte{ - // testEvent.GetReportSecretKey(): testReportWithMaxEventsData, - // }, - // }, - // ), - // namespace: "default", - // appSlug: "test-app-slug", - // event: testEvent, - // }, - // wantNumEvents: testEvent.GetReportEventLimit(), - // }, + { + name: "secret exists with max number of events", + args: CreateReportEventTestArgs{ + clientset: fake.NewSimpleClientset( + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testReport.GetSecretName("test-app-slug"), + Namespace: "default", + Labels: kotsadmtypes.GetKotsadmLabels(), + }, + Data: map[string][]byte{ + testReport.GetSecretKey(): testReportWithMaxEventsData, + }, + }, + ), + namespace: "default", + appSlug: "test-app-slug", + report: testReport, + }, + wantNumEvents: ReportEventLimit, + }, } return tests From 2f261b09df78cdb35645e816cea181b77cffbc4f Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Wed, 25 Oct 2023 21:29:05 +0000 Subject: [PATCH 4/6] rename kots_version to user_agent --- pkg/api/reporting/types/types.go | 2 +- pkg/reporting/app.go | 2 +- pkg/reporting/app_airgap.go | 2 +- pkg/reporting/instance_report.go | 2 +- pkg/reporting/preflight_airgap.go | 2 +- pkg/reporting/preflight_report.go | 2 +- pkg/reporting/report_test.go | 8 ++++---- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/api/reporting/types/types.go b/pkg/api/reporting/types/types.go index 527d741e85..ec8ac91333 100644 --- a/pkg/api/reporting/types/types.go +++ b/pkg/api/reporting/types/types.go @@ -11,7 +11,7 @@ type ReportingInfo struct { KurlNodeCountReady int `json:"kurl_node_count_ready"` K8sVersion string `json:"k8s_version"` K8sDistribution string `json:"k8s_distribution"` - KOTSVersion string `json:"kots_version"` + UserAgent string `json:"user_agent"` KOTSInstallID string `json:"kots_install_id"` KURLInstallID string `json:"kurl_install_id"` IsGitOpsEnabled bool `json:"is_gitops_enabled"` diff --git a/pkg/reporting/app.go b/pkg/reporting/app.go index 11675278f9..744e8f35ce 100644 --- a/pkg/reporting/app.go +++ b/pkg/reporting/app.go @@ -182,7 +182,7 @@ func GetReportingInfo(appID string) *types.ReportingInfo { InstanceID: appID, KOTSInstallID: os.Getenv("KOTS_INSTALL_ID"), KURLInstallID: os.Getenv("KURL_INSTALL_ID"), - KOTSVersion: buildversion.Version(), + UserAgent: buildversion.GetUserAgent(), } clientset, err := k8sutil.GetClientset() diff --git a/pkg/reporting/app_airgap.go b/pkg/reporting/app_airgap.go index 216935453a..2f31072d0d 100644 --- a/pkg/reporting/app_airgap.go +++ b/pkg/reporting/app_airgap.go @@ -54,7 +54,7 @@ func GetInstanceReport(licenseID string, reportingInfo *types.ReportingInfo) *In KurlNodeCountReady: reportingInfo.KurlNodeCountReady, K8sVersion: reportingInfo.K8sVersion, K8sDistribution: reportingInfo.K8sDistribution, - KotsVersion: reportingInfo.KOTSVersion, + UserAgent: reportingInfo.UserAgent, KotsInstallID: reportingInfo.KOTSInstallID, KurlInstallID: reportingInfo.KURLInstallID, IsGitOpsEnabled: reportingInfo.IsGitOpsEnabled, diff --git a/pkg/reporting/instance_report.go b/pkg/reporting/instance_report.go index f1271d1ae3..8c2eb06701 100644 --- a/pkg/reporting/instance_report.go +++ b/pkg/reporting/instance_report.go @@ -24,7 +24,7 @@ type InstanceReportEvent struct { KurlNodeCountReady int `json:"kurl_node_count_ready"` K8sVersion string `json:"k8s_version"` K8sDistribution string `json:"k8s_distribution,omitempty"` - KotsVersion string `json:"kots_version"` + UserAgent string `json:"user_agent"` KotsInstallID string `json:"kots_install_id,omitempty"` KurlInstallID string `json:"kurl_install_id,omitempty"` IsGitOpsEnabled bool `json:"is_gitops_enabled"` diff --git a/pkg/reporting/preflight_airgap.go b/pkg/reporting/preflight_airgap.go index a6018d8823..cdad45749b 100644 --- a/pkg/reporting/preflight_airgap.go +++ b/pkg/reporting/preflight_airgap.go @@ -32,7 +32,7 @@ func (r *AirgapReporter) SubmitPreflightData(license *kotsv1beta1.License, appID IsCLI: isCLI, PreflightStatus: preflightStatus, AppStatus: appStatus, - KotsVersion: buildversion.Version(), + UserAgent: buildversion.GetUserAgent(), }, }, } diff --git a/pkg/reporting/preflight_report.go b/pkg/reporting/preflight_report.go index 9b4a521d9c..f8b918cde0 100644 --- a/pkg/reporting/preflight_report.go +++ b/pkg/reporting/preflight_report.go @@ -24,7 +24,7 @@ type PreflightReportEvent struct { IsCLI bool `json:"is_cli"` PreflightStatus string `json:"preflight_status"` AppStatus string `json:"app_status"` - KotsVersion string `json:"kots_version"` + UserAgent string `json:"user_agent"` } func (r *PreflightReport) GetType() ReportType { diff --git a/pkg/reporting/report_test.go b/pkg/reporting/report_test.go index b6c46d3bc8..6cd8f53462 100644 --- a/pkg/reporting/report_test.go +++ b/pkg/reporting/report_test.go @@ -30,7 +30,7 @@ func Test_EncodeDecodeAirgapReport(t *testing.T) { KurlNodeCountReady: 3, K8sVersion: "1.28.0", K8sDistribution: "kurl", - KotsVersion: "1.100.0", + UserAgent: "KOTS/1.100.0", KotsInstallID: "test-kots-install-id", KurlInstallID: "test-kurl-install-id", IsGitOpsEnabled: true, @@ -71,7 +71,7 @@ func Test_EncodeDecodeAirgapReport(t *testing.T) { IsCLI: true, PreflightStatus: "pass", AppStatus: "ready", - KotsVersion: "1.100.0", + UserAgent: "KOTS/1.100.0", }, }, } @@ -101,7 +101,7 @@ func Test_CreateReportEvent(t *testing.T) { KurlNodeCountReady: 3, K8sVersion: "1.28.0", K8sDistribution: "kurl", - KotsVersion: "1.100.0", + UserAgent: "KOTS/1.100.0", KotsInstallID: "test-kots-install-id", KurlInstallID: "test-kurl-install-id", IsGitOpsEnabled: true, @@ -134,7 +134,7 @@ func Test_CreateReportEvent(t *testing.T) { IsCLI: true, PreflightStatus: "pass", AppStatus: "ready", - KotsVersion: "1.100.0", + UserAgent: "KOTS/1.100.0", }, }, } From 4c482a94f411264e299729d89f5c32a16ceca62a Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Wed, 25 Oct 2023 21:38:26 +0000 Subject: [PATCH 5/6] clean up some unused code --- pkg/reporting/report.go | 2 -- pkg/reporting/report_test.go | 6 ------ 2 files changed, 8 deletions(-) diff --git a/pkg/reporting/report.go b/pkg/reporting/report.go index 020969e372..584d682aa5 100644 --- a/pkg/reporting/report.go +++ b/pkg/reporting/report.go @@ -37,8 +37,6 @@ type Report interface { GetMtx() *sync.Mutex } -type ReportEvent interface{} - var _ Report = &InstanceReport{} var _ Report = &PreflightReport{} diff --git a/pkg/reporting/report_test.go b/pkg/reporting/report_test.go index 6cd8f53462..7dd916b915 100644 --- a/pkg/reporting/report_test.go +++ b/pkg/reporting/report_test.go @@ -189,12 +189,6 @@ func createTestsForEvent(t *testing.T, testReport Report) []CreateReportEventTes testReportWithMaxEventsData, err := EncodeReport(testReport) require.NoError(t, err) - type args struct { - clientset kubernetes.Interface - namespace string - appSlug string - event ReportEvent - } tests := []CreateReportEventTest{ { name: "secret does not exist", From 49ac1d02f7689ef852381855099ac5eba0b2471f Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Fri, 27 Oct 2023 17:07:59 +0000 Subject: [PATCH 6/6] naming updates --- pkg/reporting/app_airgap.go | 4 ++-- pkg/reporting/report_test.go | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/reporting/app_airgap.go b/pkg/reporting/app_airgap.go index 2f31072d0d..e8cb5c0a14 100644 --- a/pkg/reporting/app_airgap.go +++ b/pkg/reporting/app_airgap.go @@ -25,7 +25,7 @@ func (r *AirgapReporter) SubmitAppInfo(appID string) error { } reportingInfo := GetReportingInfo(appID) - report := GetInstanceReport(license.Spec.LicenseID, reportingInfo) + report := BuildInstanceReport(license.Spec.LicenseID, reportingInfo) if err := AppendReport(r.clientset, util.PodNamespace, a.Slug, report); err != nil { return errors.Wrap(err, "failed to append instance report") @@ -34,7 +34,7 @@ func (r *AirgapReporter) SubmitAppInfo(appID string) error { return nil } -func GetInstanceReport(licenseID string, reportingInfo *types.ReportingInfo) *InstanceReport { +func BuildInstanceReport(licenseID string, reportingInfo *types.ReportingInfo) *InstanceReport { // not using the "cursor" packages because it doesn't provide access to the underlying int64 downstreamSequence, err := strconv.ParseUint(reportingInfo.Downstream.Cursor, 10, 64) if err != nil { diff --git a/pkg/reporting/report_test.go b/pkg/reporting/report_test.go index 7dd916b915..0d43129692 100644 --- a/pkg/reporting/report_test.go +++ b/pkg/reporting/report_test.go @@ -85,7 +85,7 @@ func Test_EncodeDecodeAirgapReport(t *testing.T) { req.Equal(testPrelightReport, decodedPreflightReport) } -func Test_CreateReportEvent(t *testing.T) { +func Test_AppendReport(t *testing.T) { // instance report testDownstreamSequence := int64(123) testInstanceReport := &InstanceReport{ @@ -139,7 +139,7 @@ func Test_CreateReportEvent(t *testing.T) { }, } - tests := append(createTestsForEvent(t, testInstanceReport), createTestsForEvent(t, testPreflightReport)...) + tests := append(createTestsForReport(t, testInstanceReport), createTestsForReport(t, testPreflightReport)...) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -164,21 +164,21 @@ func Test_CreateReportEvent(t *testing.T) { } } -type CreateReportEventTest struct { +type AppendReportTest struct { name string - args CreateReportEventTestArgs + args AppendReportTestArgs wantNumEvents int wantErr bool } -type CreateReportEventTestArgs struct { +type AppendReportTestArgs struct { clientset kubernetes.Interface namespace string appSlug string report Report } -func createTestsForEvent(t *testing.T, testReport Report) []CreateReportEventTest { +func createTestsForReport(t *testing.T, testReport Report) []AppendReportTest { testReportWithOneEventData, err := EncodeReport(testReport) require.NoError(t, err) @@ -189,10 +189,10 @@ func createTestsForEvent(t *testing.T, testReport Report) []CreateReportEventTes testReportWithMaxEventsData, err := EncodeReport(testReport) require.NoError(t, err) - tests := []CreateReportEventTest{ + tests := []AppendReportTest{ { name: "secret does not exist", - args: CreateReportEventTestArgs{ + args: AppendReportTestArgs{ clientset: fake.NewSimpleClientset(), namespace: "default", appSlug: "test-app-slug", @@ -202,7 +202,7 @@ func createTestsForEvent(t *testing.T, testReport Report) []CreateReportEventTes }, { name: "secret exists with an existing event", - args: CreateReportEventTestArgs{ + args: AppendReportTestArgs{ clientset: fake.NewSimpleClientset( &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -223,7 +223,7 @@ func createTestsForEvent(t *testing.T, testReport Report) []CreateReportEventTes }, { name: "secret exists without data", - args: CreateReportEventTestArgs{ + args: AppendReportTestArgs{ clientset: fake.NewSimpleClientset( &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -241,7 +241,7 @@ func createTestsForEvent(t *testing.T, testReport Report) []CreateReportEventTes }, { name: "secret exists with max number of events", - args: CreateReportEventTestArgs{ + args: AppendReportTestArgs{ clientset: fake.NewSimpleClientset( &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{