Go Tableau Extract Refresh API exe help - Index out of Range



我一直在尝试学习Go作为速成班。我的任务是构建一个小程序,该程序接受令牌名称、秘密令牌、数据源名称和用于检查tableau提取刷新的当前状态的参数。我被一个看起来很简单的解析错误卡住了,但是找不到简单的错误。下面是代码:

package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
const URL = "https://xxx.xxxx.com/api/3.11/"
func main() {
// get the login details from command line args
tokenName := flag.String("token", "", "Personal Access Token Name")
tokenSecret := flag.String("secret", "", "Personal Access Token Secret")
datasourceName := flag.String("datasrc", "", "Datasource Name")
refreshInterval := flag.Int("refresh", 5, "Refresh Interval in minutes")
flag.Parse()
// if login details are not provided, prompt for them
if *tokenName == "" || *tokenSecret == "" {
fmt.Println("Usage:")
fmt.Println("  -token     : Personal Access Token Name")
fmt.Println("  -secret : Personal Access Token Secret")
fmt.Println("  -datasrc  : Datasource Name")
fmt.Println("  -refresh  : Refresh Interval in minutes (Optional. Default is 5)")
fmt.Println("n  Example:")
fmt.Println("  " + os.Args[0] + " -token <Personal-Access-Token-Name> -secret <Personal-Access-Token-Secret> -siteid <server-admin-site-id>")
return
}
// Sign in
internalSiteID, authToken := signIn(*tokenName, *tokenSecret)
fmt.Printf("internalSiteID: %s, authToken: %sn", internalSiteID, authToken)
// Get datasource details
datasrcID := getDatasrcDetails(internalSiteID, *datasourceName, authToken)
fmt.Printf("datasrcID: %sn", datasrcID)
// Run Refresh Extract
jobID := runRefreshExtract(internalSiteID, authToken, datasrcID)
// schedule a refresh status every 5 minutes
ticker := time.NewTicker(time.Duration(*refreshInterval) * time.Minute)
for range ticker.C {
refreshJobStatus(internalSiteID, authToken, jobID)
}
}
// signIn returns the internal site ID and auth token.
func signIn(tokenName, tokenSecret string) (internalSiteID string, authToken string) {
// Sign In
type (
// Response body
User struct {
ID string `json:"id"`
}
Site struct {
Id         string `json:"id"`
ContentUrl string `json:"contentUrl"`
}
Credentials struct {
Site        Site   `json:"site"`
User        User   `json:"user"`
NamedToken  string `json:"personalAccessTokenName"`
SecretToken string `json:"personalAccessTokenSecret"`
Token       string `json:"token"`
}
SignIn struct {
Credentials Credentials `json:"credentials"`
}
// Request body
SignInRequest struct {
Credentials Credentials `json:"credentials"`
}
)
// make sign in request
signInRequest := SignInRequest{
Credentials: Credentials{
NamedToken:  tokenName,
SecretToken: tokenSecret,
Site: Site{
ContentUrl: "", //trying to run without the site id
},
},
}
// generate request for sign-in as a JSON string
signInData, err := json.Marshal(signInRequest)
if err != nil {
fmt.Printf("Error marshalling sign in request: %sn", err)
return
}
// send request
resp, err := http.Post(URL+"auth/signin",
"application/json",
bytes.NewBuffer(signInData),
)
if err != nil {
fmt.Printf("Error marshalling sign in request: %sn", err)
return
}
// read response
defer resp.Body.Close()
// parse response
var signInResponse SignIn
err = json.NewDecoder(resp.Body).Decode(&signInResponse)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
// return internal site ID and auth token
internalSiteID = signInRequest.Credentials.Site.Id
authToken = signInRequest.Credentials.Token
return
}
// getDatasrcDetails returns the datasource details.
func getDatasrcDetails(internalSiteID, datasrcName, authToken string) (datasourceID string) {
type (
Pagination struct {
PageNumber     string `json:"pageNumber"`
PageSize       string `json:"pageSize"`
TotalAvailable string `json:"totalAvailable"`
}
Project struct {
ID   string `json:"id"`
Name string `json:"name"`
}
Owner struct {
ID string `json:"id"`
}
Tags struct {
}
Datasource struct {
Project         Project   `json:"project"`
Owner           Owner     `json:"owner"`
Tags            Tags      `json:"tags"`
ContentURL      string    `json:"contentUrl"`
CreatedAt       time.Time `json:"createdAt"`
EncryptExtracts string    `json:"encryptExtracts"`
ID              string    `json:"id"`
IsCertified     bool      `json:"isCertified"`
Name            string    `json:"name"`
Type            string    `json:"type"`
UpdatedAt       time.Time `json:"updatedAt"`
WebpageURL      string    `json:"webpageUrl"`
}
Datasources struct {
Datasource []Datasource `json:"datasource"`
}
Response struct {
Pagination  Pagination  `json:"pagination"`
Datasources Datasources `json:"datasources"`
}
)
req, err := http.NewRequest("GET", URL+"sites/"+internalSiteID+"/datasources?filter=name:eq:"+datasrcName, nil)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
req.Header.Set("X-Tableau-Auth", authToken)
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
defer resp.Body.Close()
// parse response
var response Response
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
// return datasource ID
datasourceID = response.Datasources.Datasource[0].ID
return
}
// runRefreshExtract runs a refresh extract request.
func runRefreshExtract(internalSiteID, authToken, datasrcID string) (jobID string) {
type (
Datasource struct {
ID   string `json:"id"`
Name string `json:"name"`
}
ExtractRefreshJob struct {
Notes      string     `json:"notes"`
Datasource Datasource `json:"datasource"`
}
Job struct {
ExtractRefreshJob ExtractRefreshJob `json:"extractRefreshJob"`
ID                string            `json:"id"`
Mode              string            `json:"mode"`
Type              string            `json:"type"`
Progress          string            `json:"progress"`
CreatedAt         time.Time         `json:"createdAt"`
StartedAt         time.Time         `json:"startedAt"`
CompletedAt       time.Time         `json:"completedAt"`
FinishCode        string            `json:"finishCode"`
}
Response struct {
Job Job `json:"job"`
}
)
body := strings.NewReader(`<tsRequest></tsRequest>`)
req, err := http.NewRequest("POST", URL+"sites/"+internalSiteID+"/datasources/"+datasrcID+"/refresh", body)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
req.Header.Set("X-Tableau-Auth", authToken)
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/xml")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
defer resp.Body.Close()
// parse response
var response Response
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
// return job ID
jobID = response.Job.ID
return
}
// refreshJobStatus checks the status of a refresh job.
func refreshJobStatus(internalSiteID, authToken, jobID string) {
req, err := http.NewRequest("GET", URL+"sites/"+internalSiteID+"/jobs/"+jobID, nil)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
req.Header.Set("X-Tableau-Auth", authToken)
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
defer resp.Body.Close()
// print response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error parsing sign in response: %sn", err)
return
}
fmt.Println("-- refresh job status: ", resp.Status, string(body), "--")
}

我得到的错误似乎很明显,但我发誓我的代码遵循API规范,在响应中返回的内容:

C:UsersxxxOneDriveDocumentsapiRefresh>main3.2 -token xxxx -secret xxxxx -datasrc xxxxx
Error parsing sign in response: invalid character '<' looking for beginning of value
internalSiteID: , authToken:
panic: runtime error: index out of range [0] with length 0
goroutine 1 [running]:
main.getDatasrcDetails({0x0?, 0xc000006018?}, {0xc00000a140?, 0x22?}, {0x0, 0x0})
C:/Users/xxxxx/OneDrive/Documents/apiRefresh/main3.2.go:202 +0x516
main.main()
C:/Users/xxxxxxxx/OneDrive/Documents/apiRefresh/main3.2.go:43 +0x23a
有谁能帮我找出我错在哪里吗?

简短回答

你正在尝试访问不存在的slice中的索引。

datasourceID = response.Datasources.Datasource[0].ID

长回答

signIn不成功,http请求不成功导致错误。signIn未能获得authToken,如您在输出中看到的。

Error parsing sign in response: invalid character '<' looking for beginning of value
internalSiteID: , authToken:

此时你的应用程序不应该继续执行。我相信在getDatasrcDetails中请求数据源将返回非OK状态码,最有可能是401 Unauthorized。之后,代码在

处出现恐慌
datasourceID = response.Datasources.Datasource[0].ID

因为len(response.Datasources.Datasource) == 0

推荐

考虑使用适当的错误处理。

func main() {
...
// Sign in
internalSiteID, authToken, err := signIn(*tokenName, *tokenSecret)
if err != nil {
// handle error
return
}
fmt.Printf("internalSiteID: %s, authToken: %sn", internalSiteID, authToken)
// Get datasource details
datasrcID, err := getDatasrcDetails(internalSiteID, *datasourceName, authToken)
if err != nil {
// handle error
return
}
fmt.Printf("datasrcID: %sn", datasrcID)
....
}

最新更新