Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## 2024-08-23 v1.35.0

- Based on Comet 24.6.10
- Fix multipart/form-data requests used in AdminMetaResourceNew.
- AdminMetaResourceNew now accepts a file path instead of the file contents.

## 2024-08-23 v1.34.0

- Based on Comet 24.6.10
Expand Down
55 changes: 34 additions & 21 deletions cometsdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)

Expand Down Expand Up @@ -4237,19 +4239,14 @@ func (c *CometAPIClient) Request(contentType, method, path string, data map[stri
}
u.Path = path

// req.Body must be set later on
req, err := http.NewRequest(strings.ToUpper(method), u.String(), nil)
if err != nil {
return nil, err
}
if c.CustomHeaders != nil {
for header, value := range c.CustomHeaders {
req.Header.Add(header, value)
}
}

var req *http.Request
switch contentType {
case "application/x-www-form-urlencoded":
// req.Body must be set later on
req, err = http.NewRequest(strings.ToUpper(method), u.String(), nil)
if err != nil {
return nil, err
}
body := url.Values{}
if data != nil {
body = url.Values(data)
Expand Down Expand Up @@ -4283,36 +4280,52 @@ func (c *CometAPIClient) Request(contentType, method, path string, data map[stri
m := multipart.NewWriter(&body)
for key, values := range data {
for _, value := range values {
m.WriteField(key, value)
file, err := os.Open(value)
if err != nil {
return nil, fmt.Errorf("error opening file: %w", err)
}
defer file.Close()
part, err := m.CreateFormFile(key, filepath.Base(value))
if err != nil {
return nil, err
}

if _, err := io.Copy(part, file); err != nil {
return nil, err
}
if err := m.Close(); err != nil {
return nil, err
}
}
}

req.Body = io.NopCloser(&body)

req, err = http.NewRequest(strings.ToUpper(method), u.String(), bytes.NewReader(body.Bytes()))
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", m.FormDataContentType())
req.Header.Add("X-Comet-Admin-Username", c.Username)

if c.SessionKey != "" {
req.Header.Add("X-Comet-Admin-AuthType", "SessionKey")
req.Header.Add("X-Comet-Admin-SessionKey", c.SessionKey)

} else if c.TOTPKey != "" {
req.Header.Add("X-Comet-Admin-AuthType", "PasswordTOTP")
req.Header.Add("X-Comet-Admin-Password", c.Password)
req.Header.Add("X-Comet-Admin-TOTP", c.TOTPKey)

c.TOTPKey = "" // Once the TOTPKey is used, it is not usable again.

} else {
req.Header.Add("X-Comet-Admin-AuthType", "Password")
req.Header.Add("X-Comet-Admin-Password", c.Password)

}

default:
return nil, fmt.Errorf("Unexpected content type: %s", contentType)
}

if c.CustomHeaders != nil {
for header, value := range c.CustomHeaders {
req.Header.Add(header, value)
}
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
Expand Down Expand Up @@ -8143,7 +8156,7 @@ func (c *CometAPIClient) AdminMetaResourceGet(Hash string) ([]byte, error) {
// You must supply administrator authentication credentials to use this API.
//
// - Params
// upload: The uploaded file contents, as a multipart/form-data part.
// upload: The path of the file to upload.
func (c *CometAPIClient) AdminMetaResourceNew(upload string) (*AdminResourceResponse, error) {
data := map[string][]string{}
var err error
Expand Down
50 changes: 40 additions & 10 deletions examples/admin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package main

This quick commandline tool allows you to list and download any available CometBackup Clients
hosted on the Comet Server you're sending requests to.
It also demonstrates how to upload a resource file using AdminMetaResourceNew.
*/

import (
Expand All @@ -28,12 +29,32 @@ func NewClient(url, username, password string) (*Client, error) {
if err != nil {
return nil, err
}
if _, errIn := client.AdminAccountSessionStart(nil); errIn != nil {
return retryWithTOTP(client, errIn, url)
}

return &Client{
client: client,
}, nil
}

func retryWithTOTP(c *sdk.CometAPIClient, errIn error, serverURL string) (client *Client, err error) {
if strings.Contains(errIn.Error(), "449") {
fmt.Printf("re-connecting to server: %s\n", serverURL)
totp, err := util.Totp()
if err != nil {
log.Fatal("Error reading TOTP: ", err)
}
client.client.TOTPKey = totp
// ReuseSessionKey is especially useful when using TOTP based authentication, otherwise you need to enter a
// new TOTPKey for every api call.
client.client.ReuseSessionKey = true
fmt.Println("Connection successful.")
return &Client{c}, nil
}
return nil, fmt.Errorf("error starting admin account session: %w", errIn)
}

func (c *Client) DownloadBrandedClient(platform int) ([]byte, error) {
return c.client.AdminBrandingGenerateClientByPlatform(platform, nil)
}
Expand Down Expand Up @@ -68,17 +89,27 @@ func (c *Client) ListAndPrintPlatforms() error {
return nil
}

func (c *Client) UploadResource(filePath string) error {
response, err := c.client.AdminMetaResourceNew(filePath)
if err != nil {
return err
}
fmt.Printf("Resource uploaded successfully. response: %s\n", response)
return nil
}

func main() {
url := flag.String("s", "http://localhost:8060", "The URL for the CometServer API")
username := flag.String("u", "", "The username to authenticate with")
password := flag.String("p", "", "The password to authenticate with")

list := flag.Bool("list", false, "List platforms available for download")
download := flag.Int("download", 0, "The id of the platform to download")
upload := flag.String("upload", "README.md", "Path to the file to upload as a resource")
flag.Parse()

if (*list && *download != 0) || (!*list && *download == 0) {
log.Fatal("Error: A command must be chosen. Choose one of either '--list' or '--download #'")
if (*list && *download != 0) || (!*list && *download == 0 && *upload == "") {
log.Fatal("Error: A command must be chosen. Choose one of '--list', '--download #', or '--upload <file>'")
}
if *username == "" || *password == "" {
var err error
Expand All @@ -91,14 +122,6 @@ func main() {
if err != nil {
log.Fatal("Error creating client: ", err)
}
totp, err := util.Totp()
if err != nil {
log.Fatal("Error reading TOTP: ", err)
}
client.client.TOTPKey = totp
// ReuseSessionKey is especially useful when using TOTP based authentication, otherwise you need to enter a
// new TOTPKey for every api call.
client.client.ReuseSessionKey = true

if *list {
err = client.ListAndPrintPlatforms()
Expand Down Expand Up @@ -142,4 +165,11 @@ func main() {

fmt.Println("Congratulations! You now have a branded Comet client: ", fileName)
}

if *upload != "" {
err := client.UploadResource(*upload)
if err != nil {
log.Fatal("Error uploading resource: ", err)
}
}
}