func (e *apiEntry) Post(method string, params url.Values) (map[string]*json.RawMessage, error) {
	params.Add("api", e.api)
	params.Add("version", e.version)
	params.Add("method", method)
	params.Add("_sid", e.session.GetSid())
	resp, err := e.session.Post(e.path, params)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			glog.Errorf("Failed closing the body: %v", err)
		}
	}()
	body, readErr := ioutil.ReadAll(resp.Body)
	if readErr != nil {
		return nil, readErr
	}
	var data responseData
	if jsonErr := json.Unmarshal(body, &data); jsonErr != nil {
		glog.V(3).Infof("Failed to parse: %s", body)
		return nil, jsonErr
	}
	if !data.Success {
		code := data.Error.Code
		msg := fmt.Sprintf("Failed to %s: %s(%d)", method, errorToDesc(code), code)
		return nil, errors.New(msg)
	}
	return data.Data, nil
}
Post sends 'POST' request to the endpoint for the method with the parameters
It returns value of 'data' field when the request succeeds, or nil if
the request fails or response does not contain data
package core
import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"strconv"
	"time"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	retry "github.com/avast/retry-go"
	"github.com/golang/glog"
	"github.com/google/go-querystring/query"
	"github.com/jparklab/synology-csi/pkg/synology/options"
)
func errorToDesc(code int) string {
	codeList := map[int]string{
		100: "Unknown error",
		101: "Invalid parameter",
		102: "The requested API does not exist",
		103: "The requested method does not exist",
		104: "The requested version does not support the functionality",
		105: "The logged in session does not have permission",
		106: "Session timeout",
		107: "Session interrupted by duplicate login",
	}
	return codeList[code]
}
func loginErrorToDesc(code int) string {
	codeList := map[int]string{
		400: "No such account or incorrect password",
		401: "Account disabled",
		402: "Permission denied",
		403: "2-step verification code required",
		404: "Failed to authenticate 2-step verification code",
		405: "App portal incorrect.",
		406: "OTP code enforced.",
		407: "Max Tries (if auto blocking is set to true).",
		408: "Password Expired Can not Change.",
		409: "Password Expired.",
		410: "Password must change (when first time use or after reset password by admin).",
		411: "Account Locked (when account max try exceed).",
	}
	result, ok := codeList[code]
	if ok {
		return result
	} else {
		return errorToDesc(code)
	}
}
type Session interface {
	GetSid() string
	Login(synoOption *options.SynologyOptions) (string, error)
	Logout() error
	Get(path string, params url.Values) (*http.Response, error)
	Post(path string, data url.Values) (*http.Response, error)
}
type securityData struct {
	Timeout int `json:"timeout"`
}
type securityResponseData struct {
	Data  securityData `json:"data"`
	Error struct {
		Code int `json:"code"`
	} `json:"error"`
	Success bool `json:"success"`
}
type responseData struct {
	Data  map[string]*json.RawMessage `json:"data"`
	Error struct {
		Code int `json:"code"`
	} `json:"error"`
	Success bool `json:"success"`
}
func (r *responseData) String() string {
	if b, err := json.Marshal(r); err != nil {
		return string(b)
	} else {
		return fmt.Sprintf("%v", err)
	}
}
type session struct {
	sid         string
	baseURL     string
	sessionName string
	options       *options.SynologyOptions
	timeoutMinute int
	lastLoginTime *time.Time
}
func NewSession(baseURL string, sessionName string) Session {
	return &session{
		baseURL:     baseURL,
		sessionName: sessionName,
	}
}
func (s *session) GetSid() string {
	return s.sid
}
func (s *session) prepareArguments() (url.Values, error) {
	return query.Values(s.options)
}
func (s *session) login() (string, error) {
	v, err := s.prepareArguments()
	if err != nil {
		glog.Errorf("Failed parsing URL parameters: %v", err)
		return "", err
	}
	var uri string
	var requestBody []byte
	var method string
	if s.options.LoginApiVersion >= 6 {
		uri = fmt.Sprintf(
			"%s/%s",
			s.baseURL,
			"auth.cgi",
		)
		method = "POST"
		requestBody = []byte(v.Encode())
	} else {
		uri = fmt.Sprintf(
			"%s/%s?%s",
			s.baseURL,
			"auth.cgi",
			v.Encode(),
		)
		method = "GET"
		requestBody = nil
	}
	authResp := responseData{}
	var body []byte
	client := &http.Client{
		CheckRedirect: nil,
	}
	err = retry.Do(
		func() error {
			glog.Infof("Logging in via %s", uri)
			var reader io.Reader
			if requestBody != nil {
				reader = bytes.NewReader(requestBody)
			}
			req, err := http.NewRequest(method, uri, reader)
			if err != nil {
				glog.Errorf("Failed making a %s request: %v", method, err)
				return err
			}
			req.Header.Set("Accept", "*/*")
			if method == "POST" {
				req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
				req.Header.Add("Content-Length", strconv.Itoa(len(requestBody)))
			}
			resp, err := client.Do(req)
			if err != nil {
				glog.Errorf("Failed logging in: %v", err)
				return err
			}
			defer func() {
				if err := resp.Body.Close(); err != nil {
					glog.Errorf("Failed closing the body: %v", err)
				}
			}()
			body, err = ioutil.ReadAll(resp.Body)
			if err != nil {
				glog.Errorf("Failed parsing response data : %v", err)
				return err
			}
			authResp = responseData{}
			if err = json.Unmarshal(body, &authResp); err != nil {
				glog.Errorf("Failed to parse login response: %s(%v)", body, err)
				return err
			}
			if !authResp.Success {
				code := authResp.Error.Code
				msg := fmt.Sprintf(
					"Failed to login: %d %s: %s",
					code, loginErrorToDesc(code), body)
				glog.Errorf(msg)
				return retry.Unrecoverable(errors.New(msg))
			}
			return nil
		},
		retry.Attempts(5),
		retry.Delay(time.Second*3),
	)
	if err != nil {
		return "", err
	}
	if err = json.Unmarshal(*authResp.Data["sid"], &s.sid); err != nil {
		glog.Errorf("Failed to parse auth authResp.Data.sid: %s(%v)", authResp.String(), err)
		return "", err
	}
	securityParams := url.Values{
		"_sid":    {s.sid},
		"api":     {"SYNO.Core.Security.DSM"},
		"version": {"1"},
		"method":  {"get"},
	}
	urlObj, _ := url.Parse(fmt.Sprintf("%s/entry.cgi", s.baseURL))
	urlObj.RawQuery = securityParams.Encode()
	secResp, err := http.Get(urlObj.String())
	if err != nil {
		return "", errors.New("Failed to get security config")
	}
	body, err = ioutil.ReadAll(secResp.Body)
	defer func() {
		if err := secResp.Body.Close(); err != nil {
			glog.Errorf("Failed closing the body: %v", err)
		}
	}()
	securityRespData := securityResponseData{}
	if err = json.Unmarshal(body, &securityRespData); err != nil {
		glog.Errorf("Failed to parse auth response: %s(%v)", body, err)
		return "", err
	}
	if !securityRespData.Success {
		glog.Errorf("Failed to query security config, set timeout to 0: (code: %d)", securityRespData.Error.Code)
		s.timeoutMinute = 0
	} else {
		s.timeoutMinute = securityRespData.Data.Timeout
	}
	now := time.Now()
	s.lastLoginTime = &now
	glog.Infof("Logged in. Timeout minute: %d", s.timeoutMinute)
	return s.sid, nil
}
func (s *session) ensureLoggedIn() error {
	if s.lastLoginTime == nil {
		return errors.New("Session has not been logged in yet")
	}
	minuteSinceLastLogin := time.Since(*s.lastLoginTime)
	if int(minuteSinceLastLogin.Minutes()) < s.timeoutMinute-1 {
		return nil
	}
	_, err := s.login()
	return err
}
func (s *session) Login(options *options.SynologyOptions) (string, error) {
	s.options = options
	return s.login()
}
func (s *session) Logout() error {
	params := url.Values{
		"_sid":    {s.sid},
		"session": {s.sessionName},
	}
	urlObj, _ := url.Parse(fmt.Sprintf("%s/auth.cgi", s.baseURL))
	urlObj.RawQuery = params.Encode()
	_, err := http.Get(urlObj.String())
	return err
}
func (s *session) Get(path string, params url.Values) (*http.Response, error) {
	if err := s.ensureLoggedIn(); err != nil {
		return nil, err
	}
	urlObj, _ := url.Parse(fmt.Sprintf("%s/%s", s.baseURL, path))
	urlObj.RawQuery = params.Encode()
	glog.V(8).Infof("Querying %s\n", urlObj.String())
	return http.Get(urlObj.String())
}
func (s *session) Post(path string, data url.Values) (*http.Response, error) {
	if err := s.ensureLoggedIn(); err != nil {
		return nil, err
	}
	targetURL := fmt.Sprintf("%s/%s", s.baseURL, path)
	glog.V(8).Infof("Postting %s: %#v\n", targetURL, data)
	return http.PostForm(targetURL, data)
}
type APIEntry interface {
	Get(method string, params url.Values) (map[string]*json.RawMessage, error)
	Post(method string, data url.Values) (map[string]*json.RawMessage, error)
}
type apiEntry struct {
	session Session
	path    string
	api     string
	version string
}
func NewAPIEntry(s Session, path string, api string, version string) APIEntry {
	return &apiEntry{
		session: s,
		path:    path,
		api:     api,
		version: version,
	}
}
func (e *apiEntry) Get(method string, params url.Values) (map[string]*json.RawMessage, error) {
	params.Add("api", e.api)
	params.Add("version", e.version)
	params.Add("method", method)
	params.Add("_sid", e.session.GetSid())
	resp, err := e.session.Get(e.path, params)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			glog.Errorf("Failed closing the body: %v", err)
		}
	}()
	body, readErr := ioutil.ReadAll(resp.Body)
	if readErr != nil {
		return nil, readErr
	}
	var data responseData
	if jsonErr := json.Unmarshal(body, &data); jsonErr != nil {
		glog.V(3).Infof("Failed to parse response: %s", body)
		return nil, jsonErr
	}
	if !data.Success {
		code := data.Error.Code
		msg := fmt.Sprintf("Failed to %s: %s(%d)", method, errorToDesc(code), code)
		return nil, errors.New(msg)
	}
	return data.Data, nil
}