Files
synology-csi/pkg/dsm/webapi/share.go
2024-08-27 10:00:29 +08:00

453 lines
13 KiB
Go

// Copyright 2021 Synology Inc.
package webapi
import (
"encoding/json"
"fmt"
"net/url"
"strconv"
log "github.com/sirupsen/logrus"
"github.com/SynologyOpenSource/synology-csi/pkg/utils"
"github.com/SynologyOpenSource/synology-csi/pkg/logger"
)
type ShareInfo struct {
Name string `json:"name"` // required
VolPath string `json:"vol_path"` // required
Desc string `json:"desc"`
EnableShareCow bool `json:"enable_share_cow"` // field for create
EnableRecycleBin bool `json:"enable_recycle_bin"`
RecycleBinAdminOnly bool `json:"recycle_bin_admin_only"`
Encryption int `json:"encryption"` // field for create
QuotaForCreate *int64 `json:"share_quota,omitempty"`
QuotaValueInMB int64 `json:"quota_value"` // field for get
SupportSnapshot bool `json:"support_snapshot"` // field for get
Uuid string `json:"uuid"` // field for get
NameOrg string `json:"name_org"` // required for clone
}
type ShareUpdateInfo struct {
Name string `json:"name"` // required
VolPath string `json:"vol_path"` // required
QuotaForCreate *int64 `json:"share_quota,omitempty"`
// Add properties you want to update to shares here
}
type ShareSnapshotInfo struct {
Uuid string `json:"ruuid"`
Time string `json:"time"`
Desc string `json:"desc"`
SnapSize string `json:"snap_size"` // the complete size of the snapshot
Lock bool `json:"lock"`
ScheduleSnapshot bool `json:"schedule_snapshot"`
}
type ShareCreateSpec struct {
Name string
ShareInfo ShareInfo
}
type ShareCloneSpec struct {
Name string
Snapshot string
ShareInfo ShareInfo
}
type ShareSnapshotCreateSpec struct {
ShareName string
Desc string
IsLocked bool
}
type SharePermissionSetSpec struct {
Name string
UserGroupType string // "local_user"/"local_group"/"system"
Permissions []*SharePermission
}
type SharePermission struct {
Name string `json:"name"`
IsReadonly bool `json:"is_readonly"`
IsWritable bool `json:"is_writable"`
IsDeny bool `json:"is_deny"`
IsCustom bool `json:"is_custom,omitempty"`
IsAdmin bool `json:"is_admin,omitempty"` // field for list
}
type NfsInfo struct {
EnableNfs bool `json:"enable_nfs"`
EnableNfsV4 bool `json:"enable_nfs_v4"`
NfsV4Domain string `json:"nfs_v4_domain"`
ReadSize int `json:"read_size"`
SupportEncryptShare int `json:"support_encrypt_share"`
SupportMajorVer int `json:"support_major_ver"`
SupportMinorVer int `json:"support_minor_ver"`
UnixPriEnable bool `json:"unix_pri_enable"`
WriteSize int `json:"write_size"`
}
func shareErrCodeMapping(errCode int, oriErr error) error {
switch errCode {
case 402: // No such share
return utils.NoSuchShareError("")
case 403: // Invalid input value
return utils.BadParametersError("")
case 3301: // already exists
return utils.AlreadyExistError("")
case 3309:
return utils.ShareReachMaxCountError("")
case 3328:
return utils.ShareSystemBusyError("")
}
if errCode >= 3300 {
return utils.ShareDefaultError{errCode}
}
return oriErr
}
// ----------------------- Share APIs -----------------------
func (dsm *DSM) ShareGet(shareName string) (ShareInfo, error) {
params := url.Values{}
params.Add("api", "SYNO.Core.Share")
params.Add("method", "get")
params.Add("version", "1")
params.Add("additional", "[\"encryption\", \"enable_share_cow\", \"recyclebin\", \"support_snapshot\", \"share_quota\"]")
params.Add("name", strconv.Quote(shareName))
info := ShareInfo{}
resp, err := dsm.sendRequest("", &info, params, "webapi/entry.cgi")
return info, shareErrCodeMapping(resp.ErrorCode, err)
}
func (dsm *DSM) ShareList() ([]ShareInfo, error) {
params := url.Values{}
params.Add("api", "SYNO.Core.Share")
params.Add("method", "list")
params.Add("version", "1")
params.Add("additional", "[\"encryption\", \"enable_share_cow\", \"recyclebin\", \"support_snapshot\", \"share_quota\"]")
type ShareInfos struct {
Shares []ShareInfo `json:"shares"`
}
resp, err := dsm.sendRequest("", &ShareInfos{}, params, "webapi/entry.cgi")
if err != nil {
return nil, shareErrCodeMapping(resp.ErrorCode, err)
}
infos, ok := resp.Data.(*ShareInfos)
if !ok {
return nil, fmt.Errorf("Failed to assert response to %T", &ShareInfos{})
}
return infos.Shares, nil
}
func (dsm *DSM) ShareCreate(spec ShareCreateSpec) error {
params := url.Values{}
params.Add("api", "SYNO.Core.Share")
params.Add("method", "create")
params.Add("version", "1")
params.Add("name", strconv.Quote(spec.Name))
js, err := json.Marshal(spec.ShareInfo)
if err != nil {
return err
}
params.Add("shareinfo", string(js))
resp, err := dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi")
return shareErrCodeMapping(resp.ErrorCode, err)
}
func (dsm *DSM) ShareClone(spec ShareCloneSpec) (string, error) {
params := url.Values{}
params.Add("api", "SYNO.Core.Share")
params.Add("method", "clone")
params.Add("version", "1")
params.Add("name", strconv.Quote(spec.Name))
// if clone from snapshot, the NameOrg must be the parent of the snapshot, or the webapi will return 3300
// if the snapshot doesn't exist, it will return 3300 too.
if spec.ShareInfo.NameOrg == "" {
return "", fmt.Errorf("Clone failed. The source name can't be empty.")
}
if spec.Snapshot != "" {
params.Add("snapshot", strconv.Quote(spec.Snapshot))
}
js, err := json.Marshal(spec.ShareInfo)
if err != nil {
return "", err
}
params.Add("shareinfo", string(js))
type ShareCreateResp struct {
Name string `json:"name"`
}
resp, err := dsm.sendRequest("", &ShareCreateResp{}, params, "webapi/entry.cgi")
if err != nil {
return "", shareErrCodeMapping(resp.ErrorCode, err)
}
shareResp, ok := resp.Data.(*ShareCreateResp)
if !ok {
return "", fmt.Errorf("Failed to assert response to %T", &ShareCreateResp{})
}
return shareResp.Name, nil
}
func (dsm *DSM) ShareDelete(shareName string) error {
params := url.Values{}
params.Add("api", "SYNO.Core.Share")
params.Add("method", "delete")
params.Add("version", "1")
params.Add("name", fmt.Sprintf("[%s]", strconv.Quote(shareName)))
resp, err := dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi")
return shareErrCodeMapping(resp.ErrorCode, err)
}
func (dsm *DSM) ShareSet(shareName string, updateInfo ShareUpdateInfo) error {
params := url.Values{}
params.Add("api", "SYNO.Core.Share")
params.Add("method", "set")
params.Add("version", "1")
params.Add("name", strconv.Quote(shareName))
js, err := json.Marshal(updateInfo)
if err != nil {
return err
}
params.Add("shareinfo", string(js))
if logger.WebapiDebug {
log.Debugln(params)
}
resp, err := dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi")
return shareErrCodeMapping(resp.ErrorCode, err)
}
func (dsm *DSM) SetShareQuota(shareInfo ShareInfo, newSizeInMB int64) error {
updateInfo := ShareUpdateInfo{
Name: shareInfo.Name,
VolPath: shareInfo.VolPath,
QuotaForCreate: &newSizeInMB,
}
return dsm.ShareSet(shareInfo.Name, updateInfo)
}
// ----------------------- Share Snapshot APIs -----------------------
func (dsm *DSM) ShareSnapshotCreate(spec ShareSnapshotCreateSpec) (string, error) {
params := url.Values{}
params.Add("api", "SYNO.Core.Share.Snapshot")
params.Add("method", "create")
params.Add("version", "1")
params.Add("name", strconv.Quote(spec.ShareName))
type SnapInfo struct {
Desc string `json:"desc"`
IsLocked bool `json:"lock"` // default true
}
snapinfo := SnapInfo{
Desc: spec.Desc,
IsLocked: spec.IsLocked,
}
js, err := json.Marshal(snapinfo)
if err != nil {
return "", err
}
params.Add("snapinfo", string(js))
var snapTime string
resp, err := dsm.sendRequest("", &snapTime, params, "webapi/entry.cgi")
if err != nil {
return "", shareErrCodeMapping(resp.ErrorCode, err)
}
return snapTime, nil // "GMT+08-2022.01.14-19.18.29"
}
func (dsm *DSM) ShareSnapshotList(name string) ([]ShareSnapshotInfo, error) {
params := url.Values{}
params.Add("api", "SYNO.Core.Share.Snapshot")
params.Add("method", "list")
params.Add("version", "2")
params.Add("name", strconv.Quote(name))
params.Add("additional", "[\"desc\", \"lock\", \"schedule_snapshot\", \"ruuid\", \"snap_size\"]")
type Infos struct {
Snapshots []ShareSnapshotInfo `json:"snapshots"`
Total int `json:"total"`
}
resp, err := dsm.sendRequest("", &Infos{}, params, "webapi/entry.cgi")
if err != nil {
return nil, shareErrCodeMapping(resp.ErrorCode, err)
}
infos, ok := resp.Data.(*Infos)
if !ok {
return nil, fmt.Errorf("Failed to assert response to %T", &Infos{})
}
return infos.Snapshots, nil
}
func (dsm *DSM) ShareSnapshotDelete(snapTime string, shareName string) error {
params := url.Values{}
params.Add("api", "SYNO.Core.Share.Snapshot")
params.Add("method", "delete")
params.Add("version", "1")
params.Add("name", strconv.Quote(shareName))
params.Add("snapshots", fmt.Sprintf("[%s]", strconv.Quote(snapTime))) // ["GMT+08-2022.01.14-19.18.29"]
var objmap []map[string]interface{}
resp, err := dsm.sendRequest("", &objmap, params, "webapi/entry.cgi")
if err != nil {
return shareErrCodeMapping(resp.ErrorCode, err)
}
if len(objmap) > 0 {
return fmt.Errorf("Failed to delete snapshot, API common error. snapshot: %s", snapTime)
}
return nil
}
// ----------------------- Share Permission APIs -----------------------
func (dsm *DSM) SharePermissionSet(spec SharePermissionSetSpec) error {
params := url.Values{}
params.Add("api", "SYNO.Core.Share.Permission")
params.Add("method", "set")
params.Add("version", "1")
params.Add("name", strconv.Quote(spec.Name))
params.Add("user_group_type", strconv.Quote(spec.UserGroupType))
js, err := json.Marshal(spec.Permissions)
if err != nil {
return err
}
params.Add("permissions", string(js))
resp, err := dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi")
return shareErrCodeMapping(resp.ErrorCode, err)
}
func (dsm *DSM) SharePermissionList(shareName string, userGroupType string) ([]SharePermission, error) {
params := url.Values{}
params.Add("api", "SYNO.Core.Share.Permission")
params.Add("method", "list")
params.Add("version", "1")
params.Add("name", strconv.Quote(shareName))
params.Add("user_group_type", strconv.Quote(userGroupType))
type SharePermissions struct {
Permissions []SharePermission `json:"items"`
}
resp, err := dsm.sendRequest("", &SharePermissions{}, params, "webapi/entry.cgi")
if err != nil {
return nil, shareErrCodeMapping(resp.ErrorCode, err)
}
infos, ok := resp.Data.(*SharePermissions)
if !ok {
return nil, fmt.Errorf("Failed to assert response to %T", &SharePermissions{})
}
return infos.Permissions, nil
}
// ----------------------- FileServ NFS SharePrivilege APIs -----------------------
type SecurityFlavor struct {
Kerbros bool `json:"kerberos"`
KerbrosIntegrity bool `json:"kerberos_integrity"`
KerbrosPrivacy bool `json:"kerberos_privacy"`
Sys bool `json:"sys"`
}
type PrivilegeRule struct {
Async bool `json:"async"`
Client string `json:"client"`
Crossmnt bool `json:"crossmnt"`
Insecure bool `json:"insecure"`
Privilege string `json:"privilege"`
RootSquash string `json:"root_squash"`
SecurityFlavor SecurityFlavor `json:"security_flavor"`
}
type SharePrivilege struct {
ShareName string `json:"share_name"`
Rule []PrivilegeRule `json:"rule"`
}
func (dsm *DSM) ShareNfsPrivilegeSave(privilege SharePrivilege) error {
params := url.Values{}
params.Add("api", "SYNO.Core.FileServ.NFS.SharePrivilege")
params.Add("method", "save")
params.Add("share_name", strconv.Quote(privilege.ShareName))
params.Add("version", "1")
js, err := json.Marshal(privilege.Rule)
if err != nil {
return err
}
params.Add("rule", string(js))
_, err = dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi")
if err != nil {
return err
}
return nil
}
func (dsm *DSM) NfsGet() (NfsInfo, error) {
params := url.Values{}
params.Add("api", "SYNO.Core.FileServ.NFS")
params.Add("method", "get")
params.Add("version", "2")
info := NfsInfo{}
_, err := dsm.sendRequest("", &info, params, "webapi/entry.cgi")
if err != nil {
return NfsInfo{}, err
}
return info, nil
}
func (dsm *DSM) NfsSet(enableV3 bool, enableV4 bool, enabledMinorVer int) error {
params := url.Values{}
params.Add("api", "SYNO.Core.FileServ.NFS")
params.Add("method", "set")
params.Add("version", "2")
params.Add("enable_nfs", strconv.FormatBool(enableV3))
params.Add("enable_nfs_v4", strconv.FormatBool(enableV4))
params.Add("enabled_minor_ver", strconv.Itoa(enabledMinorVer))
_, err := dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi")
if err != nil {
return err
}
return nil
}