diff --git a/hack/dev/skaffoldcache.Dockerfile b/hack/dev/skaffoldcache.Dockerfile index 6669108557..70f7d7efc7 100644 --- a/hack/dev/skaffoldcache.Dockerfile +++ b/hack/dev/skaffoldcache.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 as deps +FROM golang:1.22 as deps RUN go install github.com/go-delve/delve/cmd/dlv@v1.7.2 diff --git a/pkg/handlers/backup.go b/pkg/handlers/backup.go index 2d932c7f1a..2d850f2e1c 100644 --- a/pkg/handlers/backup.go +++ b/pkg/handlers/backup.go @@ -34,6 +34,12 @@ func (h *Handler) CreateApplicationBackup(w http.ResponseWriter, r *http.Request Success: false, } + if util.IsEmbeddedCluster() { + createApplicationBackupResponse.Error = "application backups are not supported in embedded clusters" + JSON(w, http.StatusForbidden, createApplicationBackupResponse) + return + } + // check minimal rbac if err := requiresKotsadmVeleroAccess(w, r); err != nil { return diff --git a/pkg/snapshot/store.go b/pkg/snapshot/store.go index 17ddf21e84..af13c7b17e 100644 --- a/pkg/snapshot/store.go +++ b/pkg/snapshot/store.go @@ -35,6 +35,7 @@ import ( kotss3 "github.com/replicatedhq/kots/pkg/s3" "github.com/replicatedhq/kots/pkg/snapshot/providers" "github.com/replicatedhq/kots/pkg/snapshot/types" + "github.com/replicatedhq/kots/pkg/util" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" veleroclientv1 "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1" "google.golang.org/api/option" @@ -1023,7 +1024,12 @@ func mapAWSBackupStorageLocationToStore(kotsadmVeleroBackendStorageLocation *vel return errors.Wrap(err, "failed to parse s3 url") } // without endpoint, the ui has no logic to figure if it is amazon-s3 or other-s3 compatible storages - if !isS3Compatible || strings.HasSuffix(u.Hostname(), ".amazonaws.com") { + shouldMapToAWS := !isS3Compatible || strings.HasSuffix(u.Hostname(), ".amazonaws.com") + if util.IsEmbeddedCluster() { + // embedded clusters only support other s3 compatible storage + shouldMapToAWS = false + } + if shouldMapToAWS { store.AWS = &types.StoreAWS{ Region: kotsadmVeleroBackendStorageLocation.Spec.Config["region"], } diff --git a/pkg/snapshot/velero.go b/pkg/snapshot/velero.go index b92f3bf625..4af14711b0 100644 --- a/pkg/snapshot/velero.go +++ b/pkg/snapshot/velero.go @@ -12,6 +12,7 @@ import ( "github.com/replicatedhq/kots/pkg/k8sutil" kotsadmresources "github.com/replicatedhq/kots/pkg/kotsadm/resources" kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types" + "github.com/replicatedhq/kots/pkg/util" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/cmd/cli/serverstatus" veleroclientv1 "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1" @@ -388,9 +389,16 @@ func getVeleroPod(ctx context.Context, clientset *kubernetes.Clientset, namespac "component": "velero", "deploy": "velero", } + labelSelector := labels.SelectorFromSet(veleroLabels) + + if util.IsEmbeddedCluster() { + labelSelector = labels.SelectorFromSet(map[string]string{ + "name": "velero", + }) + } veleroPods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{ - LabelSelector: labels.SelectorFromSet(veleroLabels).String(), + LabelSelector: labelSelector.String(), }) if err != nil { return "", errors.Wrap(err, "failed to list velero pods before restarting") @@ -421,6 +429,12 @@ func getNodeAgentPods(ctx context.Context, clientset *kubernetes.Clientset, name labelSelector := labels.NewSelector() labelSelector = labelSelector.Add(*componentReq, *nameReq) + if util.IsEmbeddedCluster() { + labelSelector = labels.SelectorFromSet(map[string]string{ + "name": "node-agent", + }) + } + nodeAgentPods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{ LabelSelector: labelSelector.String(), }) diff --git a/web/src/Root.tsx b/web/src/Root.tsx index 35110e0db9..2aa10be0e6 100644 --- a/web/src/Root.tsx +++ b/web/src/Root.tsx @@ -635,6 +635,9 @@ const Root = () => { } @@ -644,6 +647,9 @@ const Root = () => { element={ } @@ -655,6 +661,9 @@ const Root = () => { // @ts-ignore } @@ -664,6 +673,9 @@ const Root = () => { element={ } diff --git a/web/src/components/apps/AppDetailPage.tsx b/web/src/components/apps/AppDetailPage.tsx index 824b40003e..9b3656ecfd 100644 --- a/web/src/components/apps/AppDetailPage.tsx +++ b/web/src/components/apps/AppDetailPage.tsx @@ -488,6 +488,7 @@ function AppDetailPage(props: Props) { app={selectedApp} isVeleroInstalled={isVeleroInstalled} isHelmManaged={props.isHelmManaged} + isEmbeddedCluster={props.isEmbeddedCluster} /> diff --git a/web/src/components/modals/DeleteSnapshotModal.tsx b/web/src/components/modals/DeleteSnapshotModal.tsx index 58ef8dbe89..722cedb57d 100644 --- a/web/src/components/modals/DeleteSnapshotModal.tsx +++ b/web/src/components/modals/DeleteSnapshotModal.tsx @@ -3,6 +3,7 @@ import { Utilities } from "@src/utilities/utilities"; import { Snapshot } from "@src/types"; interface DeleteSnapshotModalProps { + featureName: string; deleteSnapshotModal: boolean; toggleConfirmDeleteModal: (snapshot: Snapshot | {}) => void; snapshotToDelete: Snapshot; @@ -14,6 +15,7 @@ interface DeleteSnapshotModalProps { export default function DeleteSnapshotModal(props: DeleteSnapshotModalProps) { const { + featureName, deleteSnapshotModal, toggleConfirmDeleteModal, snapshotToDelete, @@ -37,7 +39,7 @@ export default function DeleteSnapshotModal(props: DeleteSnapshotModalProps) {

- Delete snapshot + Delete {featureName}

{deleteErr ? (

@@ -45,8 +47,8 @@ export default function DeleteSnapshotModal(props: DeleteSnapshotModalProps) {

) : null}

- Are you sure you want do permanently delete a snapshot? This action - cannot be reversed. + Are you sure you want to permanently delete a {featureName}? This + action cannot be reversed.

@@ -87,7 +89,9 @@ export default function DeleteSnapshotModal(props: DeleteSnapshotModalProps) { }} disabled={deletingSnapshot} > - {deletingSnapshot ? "Deleting snapshot" : "Delete snapshot"} + {deletingSnapshot + ? `Deleting ${featureName}` + : `Delete ${featureName}`}
diff --git a/web/src/components/shared/NavBar.tsx b/web/src/components/shared/NavBar.tsx index 182e2fc3cd..4495573810 100644 --- a/web/src/components/shared/NavBar.tsx +++ b/web/src/components/shared/NavBar.tsx @@ -264,7 +264,9 @@ export class NavBar extends PureComponent { onClick={this.handleGoToSnapshots} className="flex flex1 u-cursor--pointer alignItems--center text u-fontSize--normal u-fontWeight--medium flex" > - Snapshots + {isEmbeddedClusterEnabled + ? "Disaster Recovery" + : "Snapshots"}
)} @@ -290,6 +292,7 @@ export class NavBar extends PureComponent { )} diff --git a/web/src/components/shared/NavBarDropdown.jsx b/web/src/components/shared/NavBarDropdown.jsx index 8a401b1362..ee62dd1595 100644 --- a/web/src/components/shared/NavBarDropdown.jsx +++ b/web/src/components/shared/NavBarDropdown.jsx @@ -3,7 +3,7 @@ import Icon from "../Icon"; import ChangePasswordModal from "../modals/ChangePasswordModal/ChangePasswordModal"; import { useEffect, useRef, useState } from "react"; -const NavBarDropdown = ({ handleLogOut, isHelmManaged }) => { +const NavBarDropdown = ({ handleLogOut, isHelmManaged, isEmbeddedCluster }) => { const [showDropdown, setShowDropdown] = useState(false); const [showModal, setShowModal] = useState(false); const testRef = useRef(null); @@ -49,7 +49,7 @@ const NavBarDropdown = ({ handleLogOut, isHelmManaged }) => {
  • setShowModal(true)}>Change password

  • - {!isHelmManaged && ( + {!isHelmManaged && !isEmbeddedCluster && (
  • Add new application

  • diff --git a/web/src/components/shared/SubNavBar/SubNavBar.jsx b/web/src/components/shared/SubNavBar/SubNavBar.jsx index 3ca867a273..63c7a341ff 100644 --- a/web/src/components/shared/SubNavBar/SubNavBar.jsx +++ b/web/src/components/shared/SubNavBar/SubNavBar.jsx @@ -13,6 +13,7 @@ export default function SubNavBar({ isAccess = false, isSnapshots = false, isHelmManaged, + isEmbeddedCluster, }) { let { slug } = app; @@ -50,13 +51,14 @@ export default function SubNavBar({ const snapshotsConfig = [ { tabName: activeTab === slug ? slug : "snapshots", - displayName: "Full Snapshots (Instance)", + displayName: isEmbeddedCluster ? "Backups" : "Full Snapshots (Instance)", to: () => `/snapshots`, }, { tabName: "partial", displayName: "Partial Snapshots (Application)", to: (slug) => `/snapshots/partial/${slug}`, + hide: isEmbeddedCluster, }, { tabName: "settings", @@ -86,6 +88,7 @@ export default function SubNavBar({ .filter(Boolean) : isSnapshots ? snapshotsConfig + .filter((link) => !link.hide) .map((link, idx) => { const generatedMenuItem = (
  • {" "} {isVeleroInstalled - ? "No snapshots yet" - : "Get started with Snapshots"}{" "} + ? `No ${featureName}s yet` + : `Get started with ${Utilities.toTitleCase(featureName)}s`}{" "}

    {isApp ? (

    - There have been no snapshots made for {app?.name} yet. You can - manually trigger snapshots or you can set up automatic snapshots to be - made on a custom schedule.{" "} + There have been no {featureName}s made for {app?.name} yet. You can + manually trigger {featureName}s or you can set up automatic{" "} + {featureName}s to be made on a custom schedule.{" "}

    ) : isVeleroInstalled ? (

    - Now that Velero is configured, you can start making snapshots. You can{" "} - create a schedule{" "} - - for automatic snapshots or you can trigger one manually whenever you’d - like. + Create a schedule + {" "} + for automatic {featureName}s or take a {featureName} manually.

    ) : (

    @@ -50,7 +55,7 @@ export default function GettingStartedSnapshots(props) { Velero {" "} installed in the cluster and configured to connect with the cloud - provider you want to send your backups to + provider you want to send your {featureName}s to

    )}
    @@ -65,8 +70,8 @@ export default function GettingStartedSnapshots(props) { > {" "} {isVeleroInstalled - ? "Start a snapshot" - : "Configure snapshot settings"} + ? `Start a ${featureName}` + : `Configure ${featureName} settings`} ) : ( )}
    diff --git a/web/src/components/snapshots/SnapshotDetails.jsx b/web/src/components/snapshots/SnapshotDetails.jsx index 901e30eb53..27b0f4d34a 100644 --- a/web/src/components/snapshots/SnapshotDetails.jsx +++ b/web/src/components/snapshots/SnapshotDetails.jsx @@ -360,7 +360,9 @@ class SnapshotDetails extends Component { key={tab} onClick={() => this.setState({ selectedScriptTab: tab })} > - {tab} + {this.props.isEmbeddedCluster + ? tab.replace("snapshot", "backup") + : tab}
  • ))} @@ -641,6 +643,8 @@ class SnapshotDetails extends Component { }; render() { + const { isEmbeddedCluster } = this.props; + const { loading, showScriptsOutput, @@ -659,6 +663,11 @@ class SnapshotDetails extends Component { errorTitle, } = this.state; + let featureName = "snapshot"; + if (isEmbeddedCluster) { + featureName = "backup"; + } + if (loading) { return (
    @@ -671,7 +680,7 @@ class SnapshotDetails extends Component {

    this.props.navigate(-1)}> - Snapshots + {Utilities.toTitleCase(featureName)}s > {snapshotDetails?.name} @@ -715,7 +724,7 @@ class SnapshotDetails extends Component {

    {" "} - This snapshot has not completed yet, check back soon{" "} + This {featureName} has not completed yet, check back soon{" "}

    ) : ( @@ -725,7 +734,7 @@ class SnapshotDetails extends Component { !isEmpty(this.postSnapshotScripts()) ? (

    - Snapshot timeline + {Utilities.toTitleCase(featureName)} timeline

    {" "} - No pre-snapshot scripts to display{" "} + No pre-{featureName} scripts to display{" "}

    ) @@ -828,7 +837,7 @@ class SnapshotDetails extends Component {

    {" "} - No post-snapshot scripts to display{" "} + No post-{featureName} scripts to display{" "}

    )} @@ -969,7 +978,7 @@ class SnapshotDetails extends Component { displayShowAllModal={showAllPreSnapshotScripts} toggleShowAllModal={this.toggleShowAllPreScripts} dataToShow={this.renderShowAllScripts(this.preSnapshotScripts())} - name="Pre-snapshot scripts" + name={`Pre-${featureName} scripts`} /> )} {showAllPostSnapshotScripts && ( @@ -977,7 +986,7 @@ class SnapshotDetails extends Component { displayShowAllModal={showAllPostSnapshotScripts} toggleShowAllModal={this.toggleShowAllPostScripts} dataToShow={this.renderShowAllScripts(this.postSnapshotScripts())} - name="Post-snapshot scripts" + name={`Post-${featureName} scripts`} /> )} {showAllWarnings && ( diff --git a/web/src/components/snapshots/SnapshotRow.jsx b/web/src/components/snapshots/SnapshotRow.jsx index 9ee583fec7..b7d204fed3 100644 --- a/web/src/components/snapshots/SnapshotRow.jsx +++ b/web/src/components/snapshots/SnapshotRow.jsx @@ -34,7 +34,7 @@ class SnapshotRow extends Component { }; render() { - const { snapshot, app } = this.props; + const { snapshot, app, hideRestore } = this.props; const isExpired = dayjs(new Date()).isSameOrAfter(snapshot?.expiresAt); return ( @@ -114,7 +114,7 @@ class SnapshotRow extends Component {
    {!isExpired && snapshot?.status !== "Deleting" && (
    - {snapshot?.status === "Completed" && ( + {snapshot?.status === "Completed" && !hideRestore && (
    @@ -449,33 +455,35 @@ class SnapshotSchedule extends Component {
    )}
    -

    Automatic snapshots

    +

    Automatic {featureName}s

    - Set up a custom schedule and retention policy for automatic - snapshots of the Admin Console and all application data. + Configure a schedule and retention policy for {featureName}s of + the admin console and all application data.

    -
    -
    - this.toggleScheduleAction("full")} - > - Full snapshots (Instance) - - this.toggleScheduleAction("partial")} - > - Partial snapshots (Application) - + {!isEmbeddedCluster && ( +
    +
    + this.toggleScheduleAction("full")} + > + Full snapshots (Instance) + + this.toggleScheduleAction("partial")} + > + Partial snapshots (Application) + +
    -
    + )} {this.state.activeTab === "partial" && (