func (c AWSStorageService) bytesFromBucket(bucketName, objectKey string) ([]byte, error) { var resp *s3.GetObjectOutput work := func() error { svc := s3.New(c.Config) params := &s3.GetObjectInput{ Bucket: aws.String(bucketName), Key: aws.String(objectKey), } var err error if resp, err = svc.GetObject(params); err != nil { Log.Println(err.Error()) return err } return nil } if err := retry.New(5*time.Second, 3, retry.DefaultBackoffFunc).Try(work); err != nil { return nil, err } return ioutil.ReadAll(resp.Body) }
// GetRepository returns a repository representation for the given Stash Project key and repository slug. func (client Client) DeleteBranchRestriction(projectKey, repositorySlug string, id int) error { retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) work := func() error { req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/rest/branch-permissions/1.0/projects/%s/repos/%s/restricted/%d", client.baseURL.String(), projectKey, repositorySlug, id), nil) if err != nil { return err } log.Printf("stash.DeleteBranchRestriction %s\n", req.URL) req.Header.Set("Accept", "application/json") req.SetBasicAuth(client.userName, client.password) responseCode, _, err := consumeResponse(req) if err != nil { return err } if responseCode != http.StatusNoContent { var reason string = "unhandled reason" switch { case responseCode == http.StatusNotFound: reason = "Not found" case responseCode == http.StatusUnauthorized: reason = "Unauthorized" } return errorResponse{StatusCode: responseCode, Reason: reason} } return nil } return retry.Try(work) }
// Get fetches existing ingresses from the api server func (listener IngressListener) Get() (Ingresses, error) { req, err := http.NewRequest("GET", fmt.Sprintf("https://%s%s", listener.masterIP, listener.getPath), nil) if err != nil { return Ingresses{}, err } req.Header.Set("Accept", "application/json") req.SetBasicAuth(listener.user, listener.password) var data []byte work := func() error { response, err := listener.httpClient.Do(req) if err != nil { return err } if response.StatusCode != 200 { return fmt.Errorf("Non-200 response: %d\n", response.StatusCode) } data, err = ioutil.ReadAll(response.Body) return err } retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) var ingresses Ingresses if err := retry.Try(work); err != nil { return ingresses, err } err = json.Unmarshal(data, &ingresses) return ingresses, err }
func (client Client) GetRawFile(repositoryProjectKey, repositorySlug, filePath, branch string) ([]byte, error) { retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) var data []byte work := func() error { req, err := http.NewRequest("GET", fmt.Sprintf("%s/projects/%s/repos/%s/browse/%s?at=%s&raw", client.baseURL.String(), strings.ToLower(repositoryProjectKey), strings.ToLower(repositorySlug), filePath, branch), nil) if err != nil { return err } log.Printf("stash.GetRawFile %s\n", req.URL) req.SetBasicAuth(client.userName, client.password) var responseCode int responseCode, data, err = consumeResponse(req) if err != nil { return err } if responseCode != http.StatusOK { var reason string = "unhandled reason" switch { case responseCode == http.StatusNotFound: reason = "Not found" case responseCode == http.StatusUnauthorized: reason = "Unauthorized" } return errorResponse{StatusCode: responseCode, Reason: reason} } return nil } return data, retry.Try(work) }
func (client Client) DeleteBranch(projectKey, repositorySlug, branchName string) error { work := func() error { buffer := bytes.NewBufferString((fmt.Sprintf(`{"name":"refs/heads/%s","dryRun":false}`, branchName))) req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/rest/branch-utils/1.0/projects/%s/repos/%s/branches", client.baseURL.String(), projectKey, repositorySlug), buffer) if err != nil { return err } req.Header.Set("Content-type", "application/json") req.SetBasicAuth(client.userName, client.password) responseCode, _, err := consumeResponse(req) if err != nil { return err } switch responseCode { case http.StatusNoContent: return nil case http.StatusBadRequest: return errorResponse{StatusCode: responseCode, Reason: "Bad Requeest"} case http.StatusUnauthorized: return errorResponse{StatusCode: responseCode, Reason: "Unauthorized"} default: return errorResponse{StatusCode: responseCode, Reason: "(unhandled reason)"} } } return retry.New(3*time.Second, 3, retry.DefaultBackoffFunc).Try(work) }
func TestOKWithTimeout(t *testing.T) { r := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) err := r.Try(func() error { return nil }) if err != nil { t.Fatalf("Unexpected error: %v\n", err) } }
// GetTags returns a map of tags indexed by tag display name for the given repository. func (client Client) GetTags(projectKey, repositorySlug string) (map[string]Tag, error) { start := 0 tags := make(map[string]Tag) morePages := true for morePages { var data []byte retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) work := func() error { req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/tags?start=%d&limit=%d", client.baseURL.String(), projectKey, repositorySlug, start, stashPageLimit), nil) if err != nil { return err } req.Header.Set("Accept", "application/json") // use credentials if we have them. If not, the repository must be public. if client.userName != "" && client.password != "" { req.SetBasicAuth(client.userName, client.password) } var responseCode int responseCode, data, err = consumeResponse(req) if err != nil { return err } if responseCode != http.StatusOK { var reason string = "unhandled reason" switch { case responseCode == http.StatusNotFound: reason = "Not found" case responseCode == http.StatusUnauthorized: reason = "Unauthorized" } return errorResponse{StatusCode: responseCode, Reason: reason} } return nil } if err := retry.Try(work); err != nil { return nil, err } var r Tags if err := json.Unmarshal(data, &r); err != nil { return nil, err } for _, tag := range r.Tags { tags[tag.DisplayID] = tag } morePages = !r.IsLastPage start = r.NextPageStart } return tags, nil }
func (gh GithubClient) GetBranches(owner, repository string) ([]GithubBranch, error) { branches := make([]GithubBranch, 0) var data []byte url := fmt.Sprintf("%s/repos/%s/%s/git/refs?client_id=%s&client_secret=%s&page=1", gh.baseURL, owner, repository, gh.ClientID, gh.ClientSecret) var response *http.Response morePages := true for morePages { work := func() error { req, err := http.NewRequest("GET", url, nil) if err != nil { return err } req.Header.Set("Accept", "application/json") response, err = httpClient.Do(req) if err != nil { return err } defer func() { response.Body.Close() }() if response.StatusCode != http.StatusOK { return fmt.Errorf("git/refs non 200 status code (%d): %s", response.StatusCode, string(data)) } data, err = ioutil.ReadAll(response.Body) if err != nil { return err } return nil } if err := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc).Try(work); err != nil { return nil, err } var b []GithubBranch if err := json.Unmarshal(data, &b); err != nil { return nil, err } branches = append(branches, b...) url = nextLink(response.Header.Get("Link")) morePages = url != "" } return branches, nil }
// GetRepositories returns a map of repositories indexed by repository URL. func (client Client) GetRepositories() (map[int]Repository, error) { start := 0 repositories := make(map[int]Repository) morePages := true for morePages { retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) var data []byte work := func() error { req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/api/1.0/repos?start=%d&limit=%d", client.baseURL.String(), start, stashPageLimit), nil) if err != nil { return err } Log.Printf("stash.GetRepositories URL %s\n", req.URL) req.Header.Set("Accept", "application/json") // use credentials if we have them. If not, the repository must be public. if client.userName != "" && client.password != "" { req.SetBasicAuth(client.userName, client.password) } var responseCode int responseCode, data, err = consumeResponse(req) if err != nil { return err } if responseCode != http.StatusOK { var reason string = "unhandled reason" switch { case responseCode == http.StatusBadRequest: reason = "Bad request." } return errorResponse{StatusCode: responseCode, Reason: reason} } return nil } if err := retry.Try(work); err != nil { return nil, err } var r Repositories err := json.Unmarshal(data, &r) if err != nil { return nil, err } for _, repo := range r.Repository { repositories[repo.ID] = repo } morePages = !r.IsLastPage start = r.NextPageStart } return repositories, nil }
func TestTimeout(t *testing.T) { r := retry.New(500*time.Millisecond, 1, retry.DefaultBackoffFunc) err := r.Try(func() error { time.Sleep(1000 * time.Millisecond) return nil }) if err == nil { t.Fatalf("Expected error\n") } fmt.Println(err) if !retry.IsTimeout(err) { t.Fatalf("Expected retry.timeoutError\n") } }
func TestRetryExceeded(t *testing.T) { r := retry.New(0*time.Second, 3, retry.DefaultBackoffFunc) tries := 0 err := r.Try(func() error { tries += 1 return errors.New("") }) if err == nil { t.Fatalf("Expecting error\n") } if tries != 3 { t.Fatalf("Expecting 3 but got %d\n", tries) } }
// GetBranches returns a map of branches indexed by branch display name for the given repository. func (client Client) GetBranches(projectKey, repositorySlug string) (map[string]Branch, error) { start := 0 branches := make(map[string]Branch) morePages := true for morePages { var data []byte retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) workit := func() error { req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/branches?start=%d&limit=%d", client.baseURL.String(), projectKey, repositorySlug, start, stashPageLimit), nil) if err != nil { return err } req.Header.Set("Accept", "application/json") req.SetBasicAuth(client.userName, client.password) var responseCode int responseCode, data, err = consumeResponse(req) if err != nil { return err } if responseCode != http.StatusOK { var reason string = "unhandled reason" switch { case responseCode == http.StatusNotFound: reason = "Not found" case responseCode == http.StatusUnauthorized: reason = "Unauthorized" } return errorResponse{StatusCode: responseCode, Reason: reason} } return nil } if err := retry.Try(workit); err != nil { return nil, err } var r Branches if err := json.Unmarshal(data, &r); err != nil { return nil, err } for _, branch := range r.Branch { branches[branch.DisplayID] = branch } morePages = !r.IsLastPage start = r.NextPageStart } return branches, nil }
// GetPullRequests returns a list of pull requests for a project / slug. func (client Client) GetPullRequests(projectKey, projectSlug, state string) ([]PullRequest, error) { start := 0 pullRequests := make([]PullRequest, 0) morePages := true for morePages { retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) var data []byte work := func() error { req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/pull-requests?state=%s&start=%d&limit=%d", client.baseURL.String(), projectKey, projectSlug, state, start, stashPageLimit), nil) if err != nil { return err } req.Header.Set("Accept", "application/json") req.SetBasicAuth(client.userName, client.password) var responseCode int responseCode, data, err = consumeResponse(req) if err != nil { return err } if responseCode != http.StatusOK { var reason string = "unhandled reason" switch { case responseCode == http.StatusBadRequest: reason = "Bad request." } return errorResponse{StatusCode: responseCode, Reason: reason} } return nil } if err := retry.Try(work); err != nil { return nil, err } var r PullRequests err := json.Unmarshal(data, &r) if err != nil { return nil, err } for _, pr := range r.PullRequests { pullRequests = append(pullRequests, pr) } morePages = !r.IsLastPage start = r.NextPageStart } return pullRequests, nil }
// GetRepository returns a repository representation for the given Stash Project key and repository slug. func (client Client) GetRepository(projectKey, repositorySlug string) (Repository, error) { retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) var r Repository work := func() error { req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s", client.baseURL.String(), projectKey, repositorySlug), nil) if err != nil { return err } Log.Printf("stash.GetRepository %s\n", req.URL) req.Header.Set("Accept", "application/json") // use credentials if we have them. If not, the repository must be public. if client.userName != "" && client.password != "" { req.SetBasicAuth(client.userName, client.password) } responseCode, data, err := consumeResponse(req) if err != nil { return err } if responseCode != http.StatusOK { var reason string = "unhandled reason" switch { case responseCode == http.StatusNotFound: reason = "Not found" case responseCode == http.StatusUnauthorized: reason = "Unauthorized" } return errorResponse{StatusCode: responseCode, Reason: reason} } err = json.Unmarshal(data, &r) if err != nil { return err } return nil } return r, retry.Try(work) }
func (client Client) GetBranchRestrictions(projectKey, repositorySlug string) (BranchRestrictions, error) { retry := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc) var branchRestrictions BranchRestrictions work := func() error { req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/branch-permissions/1.0/projects/%s/repos/%s/restricted", client.baseURL.String(), projectKey, repositorySlug), nil) if err != nil { return err } log.Printf("stash.GetBranchRestrictions %s\n", req.URL) req.Header.Set("Accept", "application/json") req.SetBasicAuth(client.userName, client.password) responseCode, data, err := consumeResponse(req) if err != nil { return err } if responseCode != http.StatusOK { var reason string = "unhandled reason" switch { case responseCode == http.StatusNotFound: reason = "Not found" case responseCode == http.StatusUnauthorized: reason = "Unauthorized" } return errorResponse{StatusCode: responseCode, Reason: reason} } err = json.Unmarshal(data, &branchRestrictions) if err != nil { return err } return nil } return branchRestrictions, retry.Try(work) }
// TokenDetail fetches the details associated with bearer token. func (client TokenDetailClient) TokenDetail(bearerToken string) (Token, error) { var token Token work := func() error { req, err := http.NewRequest("GET", client.endpointURL, nil) req.Header.Add("Authorization", "Bearer: "+bearerToken) resp, err := client.httpClient.Do(req) if err != nil { return err } data, err := ioutil.ReadAll(resp.Body) if err != nil { return err } if err := json.Unmarshal(data, &token); err != nil { return err } return nil } retry := retry.New(3*time.Second, 3, func(attempts uint) { if attempts == 0 { return } time.Sleep((1 << attempts) * time.Second) }) err := retry.Try(work) if err != nil { return Token{}, err } return token, nil }
func assembleProjects(scriptsRepo, scriptsRepoBranch string) (map[string]v1.Project, error) { projects := make(map[string]v1.Project, 0) work := func() error { Log.Printf("Clone build-scripts repository...\n") cloneDirectory, err := ioutil.TempDir("", "repoclone-") defer func() { os.RemoveAll(cloneDirectory) }() if err != nil { return err } if err := gittools.Clone(scriptsRepo, scriptsRepoBranch, cloneDirectory, true); err != nil { return err } // Build scripts are the anchors for a project. If build.sh does not exist the project is skipped. buildScripts, err := filesByRegex(cloneDirectory, buildScriptRegex) if err != nil { return err } descriptorFiles, err := filesByRegex(cloneDirectory, projectDescriptorRegex) if err != nil { return err } sidecarFiles, err := filesByRegex(cloneDirectory, sideCarRegex) if err != nil { return err } buildScriptMap := indexFilesByTeamProject(buildScripts) descriptorMap := indexFilesByTeamProject(descriptorFiles) sidecarMap := indexSidecarsByTeamProject(sidecarFiles) for k, _ := range buildScriptMap { if _, present := descriptorMap[k]; !present { Log.Printf("Skipping project without a descriptor: %s\n", k) continue } descriptorData, err := ioutil.ReadFile(descriptorMap[k]) if err != nil { Log.Println(err) continue } descriptor, err := descriptorForTeamProject(descriptorData) if err != nil { Log.Println(err) continue } if descriptor.Image == "" { Log.Printf("Skipping project %s without descriptor build image: %+v\n", k, descriptor) continue } sidecars := readSidecars(sidecarMap[k]) parts := strings.Split(k, "/") p := v1.Project{ Team: parts[0], ProjectName: parts[1], Descriptor: descriptor, Sidecars: sidecars, } projects[k] = p } return nil } err := retry.New(32*time.Second, 5, func(attempts uint) { if attempts == 0 { return } Log.Printf("Wait for clone-repository with-backoff try %d\n", attempts+1) time.Sleep((1 << attempts) * time.Second) }).Try(work) if err != nil { return nil, err } return projects, nil }
func (builder DefaultBuilder) PodWatcher() { var conn *websocket.Conn type PodWatch struct { Object struct { Meta k8stypes.TypeMeta `json:",inline"` ObjectMeta k8stypes.ObjectMeta `json:"metadata,omitempty"` Status k8stypes.PodStatus `json:"status"` } `json:"object"` } work := func() error { originURL, err := url.Parse(builder.MasterURL + "/api/v1/watch/namespaces/decap/pods?watch=true&labelSelector=type=decap-build") if err != nil { return err } serviceURL, err := url.Parse("wss://" + originURL.Host + "/api/v1/watch/namespaces/decap/pods?watch=true&labelSelector=type=decap-build") if err != nil { return err } var hdrs http.Header if builder.apiToken != "" { hdrs = map[string][]string{"Authorization": []string{"Bearer " + builder.apiToken}} } else { hdrs = map[string][]string{"Authorization": []string{"Basic " + base64.StdEncoding.EncodeToString([]byte(builder.UserName+":"+builder.Password))}} } cfg := websocket.Config{ Location: serviceURL, Origin: originURL, TlsConfig: &tls.Config{InsecureSkipVerify: true}, Header: hdrs, Version: websocket.ProtocolVersionHybi13, } if conn, err = websocket.DialConfig(&cfg); err != nil { return err } return nil } err := retry.New(5*time.Second, 60, retry.DefaultBackoffFunc).Try(work) if err != nil { Log.Printf("Error opening websocket connection. Will be unable to reap exited pods.: %v\n", err) return } Log.Print("Watching pods on websocket") var msg string for { err := websocket.Message.Receive(conn, &msg) if err != nil { if err == io.EOF { break } Log.Println("Couldn't receive msg " + err.Error()) } var pod PodWatch if err := json.Unmarshal([]byte(msg), &pod); err != nil { Log.Println(err) continue } var deletePod bool for _, status := range pod.Object.Status.ContainerStatuses { if status.Name == "build-server" && status.State.Terminated != nil && status.State.Terminated.ContainerID != "" { deletePod = true break } } if deletePod { // Mark the pod as deleted in etcd so subsequent events don't drive a 2nd deletion attempt _, err := builder.Locker.Lock("/pods/"+pod.Object.ObjectMeta.Name, "anyvalue") if err == nil { // for now just report on what we would have done vs doing it Log.Printf("Would have deleted pod: %s\n", pod.Object.ObjectMeta.Name) if true { continue } if err := builder.DeletePod(pod.Object.ObjectMeta.Name); err != nil { Log.Print(err) } else { Log.Printf("Pod deleted: %s\n", pod.Object.ObjectMeta.Name) } } } } }
func (gh GithubClient) GetRefs(owner, repository string) ([]v1.Ref, error) { var refs []GithubRef var data []byte url := fmt.Sprintf("%s/repos/%s/%s/git/refs?client_id=%s&client_secret=%s&page=1", gh.BaseURL, owner, repository, gh.Username, gh.Password) var response *http.Response morePages := true for morePages { work := func() error { req, err := http.NewRequest("GET", url, nil) if err != nil { return err } req.Header.Set("Accept", "application/json") response, err = gh.httpClient.Do(req) if err != nil { return err } defer func() { response.Body.Close() }() if response.StatusCode != http.StatusOK { return fmt.Errorf("git/refs non 200 status code (%d): %s", response.StatusCode, string(data)) } data, err = ioutil.ReadAll(response.Body) if err != nil { return err } return nil } if err := retry.New(3*time.Second, 3, retry.DefaultBackoffFunc).Try(work); err != nil { return nil, err } var b []GithubRef if err := json.Unmarshal(data, &b); err != nil { return nil, err } refs = append(refs, b...) url = gh.nextLink(response.Header.Get("Link")) morePages = url != "" } genericRefs := make([]v1.Ref, len(refs)) for i, v := range refs { var refType string switch v.Object.Type { case "tag": refType = "tag" case "commit": refType = "commit" default: refType = "__unsupported" } b := v1.Ref{RefID: v.Ref, Type: refType} genericRefs[i] = b } return genericRefs, nil }
// Listen listens for new ingresses and seeds the cache with the value of init. func (listener IngressListener) Listen(init Ingresses) { go ingressMux(init) tlsConfig := &tls.Config{} if listener.masterCACertPath == "" { tlsConfig.InsecureSkipVerify = true Log.Println("Trusting any kube-master certificate") } else { caCert, err := ioutil.ReadFile(listener.masterCACertPath) if err != nil { Log.Fatalf("Cannot read CA cert file %s: %v\n", listener.masterCACertPath, err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsConfig.RootCAs = caCertPool Log.Println("kube master secured with TLS") } websocket.DefaultDialer.TLSClientConfig = tlsConfig u := url.URL{Scheme: "wss", Host: listener.masterIP, Path: listener.watchPath} wsHeaders := http.Header{ "Authorization": []string{"Basic " + base64.StdEncoding.EncodeToString([]byte(listener.user+":"+listener.password))}, "Origin": []string{"https://" + listener.masterIP + listener.watchPath}, } var c *websocket.Conn work := func() error { var err error c, _, err = websocket.DefaultDialer.Dial(u.String(), wsHeaders) if err != nil { return err } return nil } retry := retry.New(16*time.Second, 5, func(attempts uint) { if attempts == 0 { return } if attempts > 2 { Log.Printf("Connect to kube master try %d\n", attempts+1) } time.Sleep((1 << attempts) * time.Second) }) err := retry.Try(work) if err != nil { Log.Fatalf("Failed to connect to master\n") } go func() { defer c.Close() for { _, message, err := c.ReadMessage() if err != nil { Log.Println("read:", err) } var prettyJSON bytes.Buffer if err := json.Indent(&prettyJSON, message, "", "\t"); err != nil { Log.Println(err) continue } var ingresses Ingresses Log.Printf("\n%s\n", string(prettyJSON.Bytes())) if err := json.Unmarshal(message, ingresses); err != nil { Log.Println(err) continue } ingressSetChan <- ingresses } }() }
// GetBuildsByProject returns logical builds by team / project. func (c AWSStorageService) GetBuildsByProject(project v1.Project, since uint64, limit uint64) ([]v1.Build, error) { var resp *dynamodb.QueryOutput work := func() error { svc := dynamodb.New(c.Config) params := &dynamodb.QueryInput{ TableName: aws.String("decap-build-metadata"), IndexName: aws.String("project-key-build-start-time-index"), KeyConditionExpression: aws.String("#pkey = :pkey and #bst > :since"), ExpressionAttributeNames: map[string]*string{ "#pkey": aws.String("project-key"), "#bst": aws.String("build-start-time"), }, ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ ":pkey": { S: aws.String(projectKey(project.Team, project.ProjectName)), }, ":since": { N: aws.String(fmt.Sprintf("%d", since)), }, }, ScanIndexForward: aws.Bool(false), Limit: aws.Int64(int64(limit)), } var err error resp, err = svc.Query(params) if err != nil { if awsErr, ok := err.(awserr.Error); ok { Log.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) if reqErr, ok := err.(awserr.RequestFailure); ok { Log.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) } } else { Log.Println(err.Error()) } return err } return nil } err := retry.New(5*time.Second, 3, retry.DefaultBackoffFunc).Try(work) if err != nil { return nil, err } var builds []v1.Build for _, v := range resp.Items { buildDuration, err := strconv.ParseUint(*v["build-duration"].N, 10, 64) if err != nil { Log.Printf("Error converting build-duration to ordinal value: %v\n", err) } buildResult, err := strconv.ParseInt(*v["build-result"].N, 10, 32) if err != nil { Log.Printf("Error converting build-result to ordinal value: %v\n", err) } buildTime, err := strconv.ParseUint(*v["build-start-time"].N, 10, 64) if err != nil { Log.Printf("Error converting build-start-time to ordinal value: %v\n", err) } build := v1.Build{ ID: *v["build-id"].S, ProjectKey: *v["project-key"].S, Branch: *v["branch"].S, Duration: buildDuration, Result: int(buildResult), UnixTime: buildTime, } builds = append(builds, build) } return builds, nil }