Skip to content

Commit

Permalink
Share support bundle with vendor (#2257)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Reed committed Oct 19, 2021
1 parent d92c96b commit ff59aa2
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 54 deletions.
31 changes: 16 additions & 15 deletions kotskinds/apis/kots/v1beta1/license_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,22 @@ type EntitlementField struct {

// LicenseSpec defines the desired state of LicenseSpec
type LicenseSpec struct {
Signature []byte `json:"signature"`
AppSlug string `json:"appSlug"`
Endpoint string `json:"endpoint,omitempty"`
CustomerName string `json:"customerName,omitempty"`
ChannelID string `json:"channelID,omitempty"`
ChannelName string `json:"channelName,omitempty"`
LicenseSequence int64 `json:"licenseSequence,omitempty"`
LicenseID string `json:"licenseID"`
LicenseType string `json:"licenseType,omitempty"`
IsAirgapSupported bool `json:"isAirgapSupported,omitempty"`
IsGitOpsSupported bool `json:"isGitOpsSupported,omitempty"`
IsIdentityServiceSupported bool `json:"isIdentityServiceSupported,omitempty"`
IsGeoaxisSupported bool `json:"isGeoaxisSupported,omitempty"`
IsSnapshotSupported bool `json:"isSnapshotSupported,omitempty"`
Entitlements map[string]EntitlementField `json:"entitlements,omitempty"`
Signature []byte `json:"signature"`
AppSlug string `json:"appSlug"`
Endpoint string `json:"endpoint,omitempty"`
CustomerName string `json:"customerName,omitempty"`
ChannelID string `json:"channelID,omitempty"`
ChannelName string `json:"channelName,omitempty"`
LicenseSequence int64 `json:"licenseSequence,omitempty"`
LicenseID string `json:"licenseID"`
LicenseType string `json:"licenseType,omitempty"`
IsAirgapSupported bool `json:"isAirgapSupported,omitempty"`
IsGitOpsSupported bool `json:"isGitOpsSupported,omitempty"`
IsIdentityServiceSupported bool `json:"isIdentityServiceSupported,omitempty"`
IsGeoaxisSupported bool `json:"isGeoaxisSupported,omitempty"`
IsSnapshotSupported bool `json:"isSnapshotSupported,omitempty"`
IsSupportBundleUploadSupported bool `json:"isSupportBundleUploadSupported,omitempty"`
Entitlements map[string]EntitlementField `json:"entitlements,omitempty"`
}

// LicenseStatus defines the observed state of License
Expand Down
2 changes: 2 additions & 0 deletions kotskinds/config/crds/kots.io_licenses.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ spec:
type: boolean
isSnapshotSupported:
type: boolean
isSupportBundleUploadEnabled:
type: boolean
licenseID:
type: string
licenseSequence:
Expand Down
2 changes: 2 additions & 0 deletions pkg/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ func RegisterSessionAuthRoutes(r *mux.Router, kotsStore store.Store, handler KOT
HandlerFunc(middleware.EnforceAccess(policy.AppSupportbundleRead, handler.DownloadSupportBundle)) // TODO: appSlug
r.Name("CollectSupportBundle").Path("/api/v1/troubleshoot/supportbundle/app/{appId}/cluster/{clusterId}/collect").Methods("POST").
HandlerFunc(middleware.EnforceAccess(policy.AppSupportbundleWrite, handler.CollectSupportBundle))
r.Name("ShareSupportBundle").Path("/api/v1/troubleshoot/app/{appSlug}/supportbundle/{bundleId}/share").Methods("POST").
HandlerFunc(middleware.EnforceAccess(policy.AppSupportbundleWrite, handler.ShareSupportBundle))

// redactor routes
r.Name("UpdateRedact").Path("/api/v1/redact/set").Methods("PUT").
Expand Down
11 changes: 11 additions & 0 deletions pkg/handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ var HandlerPolicyTests = map[string][]HandlerPolicyTest{
ExpectStatus: http.StatusOK,
},
},
"ShareSupportBundle": {
{
Vars: map[string]string{"appSlug": "my-app", "bundleId": "234"},
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
SessionRoles: []string{rbac.ClusterAdminRoleID},
Calls: func(storeRecorder *mock_store.MockStoreMockRecorder, handlerRecorder *mock_handlers.MockKOTSHandlerMockRecorder) {
handlerRecorder.ShareSupportBundle(gomock.Any(), gomock.Any())
},
ExpectStatus: http.StatusOK,
},
},

// redactor routes
"UpdateRedact": {
Expand Down
1 change: 1 addition & 0 deletions pkg/handlers/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type KOTSHandler interface {
GetSupportBundleRedactions(w http.ResponseWriter, r *http.Request) // TODO: appSlug
DownloadSupportBundle(w http.ResponseWriter, r *http.Request) // TODO: appSlug
CollectSupportBundle(w http.ResponseWriter, r *http.Request)
ShareSupportBundle(w http.ResponseWriter, r *http.Request)

// redactor routes
UpdateRedact(w http.ResponseWriter, r *http.Request)
Expand Down
81 changes: 42 additions & 39 deletions pkg/handlers/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,20 @@ type SyncLicenseRequest struct {
}

type LicenseResponse struct {
ID string `json:"id"`
Assignee string `json:"assignee"`
ExpiresAt time.Time `json:"expiresAt"`
ChannelName string `json:"channelName"`
LicenseSequence int64 `json:"licenseSequence"`
LicenseType string `json:"licenseType"`
Entitlements []EntitlementResponse `json:"entitlements"`
IsAirgapSupported bool `json:"isAirgapSupported"`
IsGitOpsSupported bool `json:"isGitOpsSupported"`
IsIdentityServiceSupported bool `json:"isIdentityServiceSupported"`
IsGeoaxisSupported bool `json:"isGeoaxisSupported"`
IsSnapshotSupported bool `json:"isSnapshotSupported"`
LastSyncedAt string `json:"lastSyncedAt"`
ID string `json:"id"`
Assignee string `json:"assignee"`
ExpiresAt time.Time `json:"expiresAt"`
ChannelName string `json:"channelName"`
LicenseSequence int64 `json:"licenseSequence"`
LicenseType string `json:"licenseType"`
Entitlements []EntitlementResponse `json:"entitlements"`
IsAirgapSupported bool `json:"isAirgapSupported"`
IsGitOpsSupported bool `json:"isGitOpsSupported"`
IsIdentityServiceSupported bool `json:"isIdentityServiceSupported"`
IsGeoaxisSupported bool `json:"isGeoaxisSupported"`
IsSnapshotSupported bool `json:"isSnapshotSupported"`
LastSyncedAt string `json:"lastSyncedAt"`
IsSupportBundleUploadSupported bool `json:"isSupportBundleUploadSupported"`
}

type SyncLicenseResponse struct {
Expand Down Expand Up @@ -139,19 +140,20 @@ func (h *Handler) SyncLicense(w http.ResponseWriter, r *http.Request) {
syncLicenseResponse.Success = true
syncLicenseResponse.Synced = synced
syncLicenseResponse.License = LicenseResponse{
ID: latestLicense.Spec.LicenseID,
Assignee: latestLicense.Spec.CustomerName,
ChannelName: latestLicense.Spec.ChannelName,
LicenseSequence: latestLicense.Spec.LicenseSequence,
LicenseType: latestLicense.Spec.LicenseType,
Entitlements: entitlements,
ExpiresAt: expiresAt,
IsAirgapSupported: latestLicense.Spec.IsAirgapSupported,
IsGitOpsSupported: latestLicense.Spec.IsGitOpsSupported,
IsIdentityServiceSupported: latestLicense.Spec.IsIdentityServiceSupported,
IsGeoaxisSupported: latestLicense.Spec.IsGeoaxisSupported,
IsSnapshotSupported: latestLicense.Spec.IsSnapshotSupported,
LastSyncedAt: foundApp.LastLicenseSync,
ID: latestLicense.Spec.LicenseID,
Assignee: latestLicense.Spec.CustomerName,
ChannelName: latestLicense.Spec.ChannelName,
LicenseSequence: latestLicense.Spec.LicenseSequence,
LicenseType: latestLicense.Spec.LicenseType,
Entitlements: entitlements,
ExpiresAt: expiresAt,
IsAirgapSupported: latestLicense.Spec.IsAirgapSupported,
IsGitOpsSupported: latestLicense.Spec.IsGitOpsSupported,
IsIdentityServiceSupported: latestLicense.Spec.IsIdentityServiceSupported,
IsGeoaxisSupported: latestLicense.Spec.IsGeoaxisSupported,
IsSnapshotSupported: latestLicense.Spec.IsSnapshotSupported,
LastSyncedAt: foundApp.LastLicenseSync,
IsSupportBundleUploadSupported: latestLicense.Spec.IsSupportBundleUploadSupported,
}

JSON(w, http.StatusOK, syncLicenseResponse)
Expand Down Expand Up @@ -189,19 +191,20 @@ func (h *Handler) GetLicense(w http.ResponseWriter, r *http.Request) {

getLicenseResponse.Success = true
getLicenseResponse.License = LicenseResponse{
ID: license.Spec.LicenseID,
Assignee: license.Spec.CustomerName,
ChannelName: license.Spec.ChannelName,
LicenseSequence: license.Spec.LicenseSequence,
LicenseType: license.Spec.LicenseType,
Entitlements: entitlements,
ExpiresAt: expiresAt,
IsAirgapSupported: license.Spec.IsAirgapSupported,
IsGitOpsSupported: license.Spec.IsGitOpsSupported,
IsIdentityServiceSupported: license.Spec.IsIdentityServiceSupported,
IsGeoaxisSupported: license.Spec.IsGeoaxisSupported,
IsSnapshotSupported: license.Spec.IsSnapshotSupported,
LastSyncedAt: foundApp.LastLicenseSync,
ID: license.Spec.LicenseID,
Assignee: license.Spec.CustomerName,
ChannelName: license.Spec.ChannelName,
LicenseSequence: license.Spec.LicenseSequence,
LicenseType: license.Spec.LicenseType,
Entitlements: entitlements,
ExpiresAt: expiresAt,
IsAirgapSupported: license.Spec.IsAirgapSupported,
IsGitOpsSupported: license.Spec.IsGitOpsSupported,
IsIdentityServiceSupported: license.Spec.IsIdentityServiceSupported,
IsGeoaxisSupported: license.Spec.IsGeoaxisSupported,
IsSnapshotSupported: license.Spec.IsSnapshotSupported,
LastSyncedAt: foundApp.LastLicenseSync,
IsSupportBundleUploadSupported: license.Spec.IsSupportBundleUploadSupported,
}

JSON(w, http.StatusOK, getLicenseResponse)
Expand Down
12 changes: 12 additions & 0 deletions pkg/handlers/mock/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 90 additions & 0 deletions pkg/handlers/troubleshoot.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,96 @@ func (h *Handler) DownloadSupportBundle(w http.ResponseWriter, r *http.Request)
io.Copy(w, f)
}

func (h *Handler) ShareSupportBundle(w http.ResponseWriter, r *http.Request) {
appSlug := mux.Vars(r)["appSlug"]
bundleID := mux.Vars(r)["bundleId"]

app, err := store.GetStore().GetAppFromSlug(appSlug)
if err != nil {
logger.Error(err)
JSON(w, http.StatusInternalServerError, nil)
return
}

license, err := store.GetStore().GetLatestLicenseForApp(app.ID)
if err != nil {
logger.Error(err)
JSON(w, http.StatusInternalServerError, nil)
return
}

if !license.Spec.IsSupportBundleUploadSupported {
logger.Errorf("License does not have support bundle sharing enabled")
JSON(w, http.StatusForbidden, nil)
return
}

bundle, err := store.GetStore().GetSupportBundle(bundleID)
if err != nil {
logger.Error(err)
JSON(w, http.StatusInternalServerError, nil)
return
}

bundleArchive, err := store.GetStore().GetSupportBundleArchive(bundle.ID)
if err != nil {
logger.Error(err)
JSON(w, http.StatusInternalServerError, nil)
return
}
defer os.RemoveAll(bundleArchive)

f, err := os.Open(bundleArchive)
if err != nil {
logger.Error(err)
JSON(w, http.StatusInternalServerError, nil)
return
}
defer f.Close()

fileStat, err := f.Stat()
if err != nil {
logger.Error(err)
JSON(w, http.StatusInternalServerError, nil)
return
}

endpoint := fmt.Sprintf("%s/supportbundle/upload/%s", license.Spec.Endpoint, license.Spec.AppSlug)

req, err := http.NewRequest("POST", endpoint, f)
if err != nil {
logger.Error(err)
JSON(w, http.StatusInternalServerError, nil)
return
}

req.Header.Set("Content-Type", "application/tar+gzip")

req.ContentLength = fileStat.Size()

req.SetBasicAuth(license.Spec.LicenseID, license.Spec.LicenseID)

resp, err := http.DefaultClient.Do(req)
if err != nil {
logger.Error(err)
JSON(w, http.StatusInternalServerError, nil)
return
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
body, err := io.ReadAll(resp.Body)
if err == nil {
logger.Errorf("Failed to share support bundle: %d: %s", resp.StatusCode, string(body))
} else {
logger.Errorf("Failed to share support bundle: %d", resp.StatusCode)
}
JSON(w, http.StatusInternalServerError, nil)
return
}

JSON(w, http.StatusOK, "")
}

func (h *Handler) CollectSupportBundle(w http.ResponseWriter, r *http.Request) {
a, err := store.GetStore().GetApp(mux.Vars(r)["appId"])
if err != nil {
Expand Down

0 comments on commit ff59aa2

Please sign in to comment.