GoLang

Simple Go script to upload a capture zip file. It retrieves an authentication token using the client_id and client_secret found in your Developer Dashboard.

Read the Quick Start Guide and the Upload Captures pages to learn more.

⚠️ To run this you need Go 1.19+.

package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "net/url"
  "os"
  "path/filepath"
)

const (
  ClientID     = "insert-it-here"
  ClientSecret = "insert-it-here"
  AuthEndpoint = "https://signin.teleport.varjo.com/oauth2/token"
  ApiBase      = "https://teleport.varjo.com"
)

type AuthResponse struct {
  AccessToken string `json:"access_token"`
}

type CreateCaptureResponse struct {
  Eid       string `json:"eid"`
  NumParts  int    `json:"num_parts"`
  ChunkSize int64  `json:"chunk_size"`
}

type UploadURLResponse struct {
  UploadURL string `json:"upload_url"`
}

type UploadedPart struct {
  Number int    `json:"number"`
  ETag   string `json:"etag"`
}

func main() {
  if len(os.Args) < 2 {
    fmt.Println("Usage: go run upload_capture.go <file>")
    os.Exit(1)
  }

  filename := os.Args[1]
  file, err := os.Open(filename)
  if err != nil {
    fmt.Println("Error opening file:", err)
    os.Exit(1)
  }
  defer file.Close()

  fileInfo, _ := file.Stat()
  bytesize := fileInfo.Size()
  fmt.Printf("Uploading capture of %d bytes...\n", bytesize)

  // Step 1: Authenticate
  token, err := getAuthToken()
  if err != nil {
    fmt.Println("Authentication failed:", err)
    os.Exit(1)
  }

  // Step 2: Create capture
  eid, numParts, chunkSize, err := createCapture(token, filename, bytesize)
  if err != nil {
    fmt.Println("Failed to create capture:", err)
    os.Exit(1)
  }

  fmt.Printf("Uploading %d parts...\n", numParts)
  parts := make([]UploadedPart, 0, numParts)

  for partNo := 1; partNo <= numParts; partNo++ {
    uploadURL, err := getUploadURL(token, eid, bytesize, partNo)
    if err != nil {
      fmt.Printf("Failed to get upload URL for part %d: %v\n", partNo, err)
      os.Exit(1)
    }

    part := make([]byte, chunkSize)
    n, err := file.Read(part)
    if err != nil && err != io.EOF {
      fmt.Println("Error reading file:", err)
      os.Exit(1)
    }

    fmt.Printf("Uploading part %d...\n", partNo)
    etag, err := uploadPart(uploadURL, part[:n])
    if err != nil {
      fmt.Printf("Failed to upload part %d: %v\n", partNo, err)
      os.Exit(1)
    }

    parts = append(parts, UploadedPart{Number: partNo, ETag: etag})
  }

  // Step 3: Notify completion
  state, err := finalizeUpload(token, eid, parts)
  if err != nil {
    fmt.Println("Failed to finalize upload:", err)
    os.Exit(1)
  }

  fmt.Println("Upload done!")
  fmt.Println("Capture state:", state)
}

func getAuthToken() (string, error) {
  data := url.Values{}
  data.Set("grant_type", "client_credentials")
  data.Set("client_id", ClientID)
  data.Set("client_secret", ClientSecret)
  data.Set("scope", "openid profile email")

  resp, err := http.PostForm(AuthEndpoint, data)
  if err != nil {
    return "", err
  }
  defer resp.Body.Close()

  if resp.StatusCode != 200 {
    return "", fmt.Errorf("status %d", resp.StatusCode)
  }

  var result AuthResponse
  if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
    return "", err
  }
  return result.AccessToken, nil
}

func createCapture(token, filename string, bytesize int64) (string, int, int64, error) {
  payload := map[string]interface{}{
    "name":              filepath.Base(filename),
    "bytesize":          bytesize,
    "input_data_format": "bulk-images",
  }
  body, _ := json.Marshal(payload)

  req, _ := http.NewRequest("POST", ApiBase+"/api/v1/captures", bytes.NewReader(body))
  req.Header.Set("Authorization", "Bearer "+token)
  req.Header.Set("Content-Type", "application/json")

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    return "", 0, 0, err
  }
  defer resp.Body.Close()

  if resp.StatusCode != 200 {
    return "", 0, 0, fmt.Errorf("status %d", resp.StatusCode)
  }

  var data CreateCaptureResponse
  if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
    return "", 0, 0, err
  }

  return data.Eid, data.NumParts, data.ChunkSize, nil
}

func getUploadURL(token, eid string, bytesize int64, partNo int) (string, error) {
  payload := map[string]interface{}{
    "eid":      eid,
    "bytesize": bytesize,
  }
  body, _ := json.Marshal(payload)

  url := fmt.Sprintf("%s/api/v1/captures/%s/create-upload-url/%d", ApiBase, eid, partNo)
  req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
  req.Header.Set("Authorization", "Bearer "+token)
  req.Header.Set("Content-Type", "application/json")

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    return "", err
  }
  defer resp.Body.Close()

  if resp.StatusCode != 200 {
    return "", fmt.Errorf("status %d", resp.StatusCode)
  }

  var data UploadURLResponse
  if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
    return "", err
  }

  return data.UploadURL, nil
}

func uploadPart(uploadURL string, data []byte) (string, error) {
  req, _ := http.NewRequest("PUT", uploadURL, bytes.NewReader(data))
  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    return "", err
  }
  defer resp.Body.Close()

  if resp.StatusCode != 200 {
    return "", fmt.Errorf("status %d", resp.StatusCode)
  }

  etag := resp.Header.Get("ETag")
  if len(etag) > 0 {
    etag = etag[1 : len(etag)-1] // remove quotes
  }
  return etag, nil
}

func finalizeUpload(token, eid string, parts []UploadedPart) (string, error) {
  payload := map[string]interface{}{
    "eid":   eid,
    "parts": parts,
  }
  body, _ := json.Marshal(payload)

  url := fmt.Sprintf("%s/api/v1/captures/%s/uploaded", ApiBase, eid)
  req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
  req.Header.Set("Authorization", "Bearer "+token)
  req.Header.Set("Content-Type", "application/json")

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    return "", err
  }
  defer resp.Body.Close()

  if resp.StatusCode != 200 {
    return "", fmt.Errorf("status %d", resp.StatusCode)
  }

  var data map[string]interface{}
  if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
    return "", err
  }
  state, _ := data["state"].(string)
  return state, nil
}