package controller import ( "github.com/go-openapi/runtime/middleware" "github.com/jmoiron/sqlx" "github.com/michaelquigley/df/dl" "github.com/openziti/zrok/v2/controller/automation" "github.com/openziti/zrok/v2/controller/store" "github.com/openziti/zrok/v2/rest_model_zrok" "github.com/openziti/zrok/v2/rest_server_zrok/operations/share" "github.com/openziti/zrok/v2/util" "github.com/pkg/errors" ) type unshareHandler struct{} func newUnshareHandler() *unshareHandler { return &unshareHandler{} } func (h *unshareHandler) Handle(params share.UnshareParams, principal *rest_model_zrok.Principal) middleware.Responder { trx, err := str.Begin() if err != nil { dl.Errorf("error starting transaction for '%v': %v", principal.Email, err) return share.NewUnshareInternalServerError() } defer func() { _ = trx.Rollback() }() shrToken := params.Body.ShareToken envZId := params.Body.EnvZID // validate environment env, err := h.validateEnvironment(envZId, principal, trx) if err != nil { dl.Errorf("environment validation failed for '%v': %v", principal.Email, err) return share.NewUnshareNotFound() } // find and validate share shr, err := h.findAndValidateShare(shrToken, env, trx) if err != nil { dl.Errorf("share validation failed for '%v': %v", principal.Email, err) return share.NewUnshareNotFound() } // deallocate ziti resources using automation framework if err := h.deallocateResources(shrToken); err != nil { dl.Warnf("error deallocating ziti resources for share '%v': %v", shrToken, err) } // send unbind mapping updates before cleaning up share name mappings if err := h.processDynamicMappings(shr.Id, trx); err != nil { dl.Errorf("error sending unbind mapping updates for '%v': %v", shrToken, err) } // clean up share name mappings if err := h.cleanupShareNameMappings(shr.Id, trx); err != nil { dl.Errorf("error cleaning up share name mappings for '%v': %v", shrToken, err) return share.NewUnshareInternalServerError() } // clean up access grants if err := str.DeleteAccessGrantsForShare(shr.Id, trx); err != nil { dl.Errorf("error deleting access grants for share '%v': %v", shrToken, err) return share.NewUnshareInternalServerError() } // delete the share record if err := str.DeleteShare(shr.Id, trx); err != nil { dl.Errorf("error deleting share '%v': %v", shrToken, err) return share.NewUnshareInternalServerError() } // commit transaction if err := trx.Commit(); err != nil { dl.Errorf("error committing transaction for '%v': %v", shrToken, err) return share.NewUnshareInternalServerError() } dl.Infof("successfully unshared '%v' for '%v'", shrToken, principal.Email) return share.NewUnshareOK() } func (h *unshareHandler) validateEnvironment(envZId string, principal *rest_model_zrok.Principal, trx *sqlx.Tx) (*store.Environment, error) { env, err := str.FindEnvironmentForAccount(envZId, int(principal.ID), trx) if err != nil { return nil, errors.Wrapf(err, "error finding environment '%v' for account '%v'", envZId, principal.Email) } return env, nil } func (h *unshareHandler) findAndValidateShare(shrToken string, env *store.Environment, trx *sqlx.Tx) (*store.Share, error) { shares, err := str.FindSharesForEnvironment(env.Id, trx) if err != nil { return nil, errors.Wrapf(err, "error finding shares for environment '%v'", env.ZId) } for _, share := range shares { if share.Token == shrToken { return share, nil } } return nil, errors.Errorf("share '%v' not found in environment '%v'", shrToken, env.ZId) } func (h *unshareHandler) deallocateResources(shrToken string) error { // get shared automation client za, err := automation.NewZitiAutomation(cfg.Ziti) if err != nil { return errors.Wrap(err, "error getting ziti automation client") } // use fluent workflow API for tag-based cleanup err = za.CleanupByTag("zrokShareToken", shrToken) if err != nil { return errors.Wrapf(err, "error cleaning up ziti resources for share '%v'", shrToken) } dl.Infof("deallocated ziti resources for share '%v'", shrToken) return nil } func (h *unshareHandler) cleanupShareNameMappings(shareId int, trx *sqlx.Tx) error { details, err := str.FindShareNameCleanupDetailsByShareId(shareId, trx) if err != nil { return errors.Wrapf(err, "error finding share name cleanup details for share '%v'", shareId) } for _, detail := range details { // only delete names that are not reserved and are not already deleted if !detail.Reserved && !detail.NameDeleted { if err := str.DeleteName(detail.NameId, trx); err != nil { return errors.Wrapf(err, "error deleting dynamically allocated name '%v'", detail.Name) } dl.Debugf("deleted dynamically allocated name '%v'", detail.Name) } // delete the share name mapping if err := str.DeleteShareNameMapping(detail.MappingId, trx); err != nil { return errors.Wrapf(err, "error deleting share name mapping '%v'", detail.MappingId) } } return nil } func (h *unshareHandler) processDynamicMappings(shareId int, trx *sqlx.Tx) error { // only send updates if dynamic proxy controller is enabled if dPCtrl == nil { return nil } details, err := str.FindShareNameCleanupDetailsByShareId(shareId, trx) if err != nil { return errors.Wrapf(err, "error finding share name cleanup details for share '%v'", shareId) } for _, detail := range details { if detail.NamespaceDeleted { dl.Warnf("namespace '%v' is deleted while unbinding share name mapping '%v'", detail.NamespaceName, detail.MappingId) continue } // find dynamic frontends for this namespace frontends, err := str.FindDynamicFrontendsForNamespace(detail.NamespaceID, trx) if err != nil { dl.Warnf("error finding dynamic frontends for namespace '%v': %v", detail.NamespaceName, err) continue } // send unbind mapping updates to each dynamic frontend for _, frontend := range frontends { frontendName := util.NameInNamespace(detail.Name, detail.NamespaceName) if err := dPCtrl.UnbindFrontendMapping(frontend.Token, frontendName, trx); err != nil { dl.Errorf("error unbinding frontend mapping from frontend '%v': %v", frontend.Token, err) // continue with other frontends rather than failing completely } else { dl.Debugf("unbound frontend mapping '%v' from dynamic frontend '%v'", frontendName, frontend.Token) } } } return nil }