// Build creates a generic property that may be a Map, List or primitive. // If it is a Map or List it will be built using the appropriate builder and constraints. func Build(val interface{}) (Property, error) { if val == nil { return nil, nil } switch reflect.TypeOf(val).Kind() { case reflect.Map: valMap, ok := val.(map[interface{}]interface{}) if !ok { return nil, bosherr.Errorf("Converting map %#v", val) } return BuildMap(valMap) case reflect.Slice: valSlice, ok := val.([]interface{}) if !ok { return nil, bosherr.Errorf("Converting slice %#v", val) } return BuildList(valSlice) default: return val, nil } }
func (p rootDevicePartitioner) Partition(devicePath string, partitions []Partition) error { existingPartitions, deviceFullSizeInBytes, err := p.getPartitions(devicePath) if err != nil { return bosherr.WrapErrorf(err, "Getting existing partitions of `%s'", devicePath) } p.logger.Debug(p.logTag, "Current partitions: %#v", existingPartitions) if len(existingPartitions) == 0 { return bosherr.Errorf("Missing first partition on `%s'", devicePath) } if p.partitionsMatch(existingPartitions[1:], partitions) { p.logger.Info(p.logTag, "Partitions already match, skipping partitioning") return nil } if len(existingPartitions) > 1 { p.logger.Error(p.logTag, "Failed to create ephemeral partitions on root device `%s'. Expected 1 partition, found %d: %s", devicePath, len(existingPartitions), existingPartitions, ) return bosherr.Errorf("Found %d unexpected partitions on `%s'", len(existingPartitions)-1, devicePath) } // To support optimal reads on HDDs and optimal erasure on SSD: use 1MiB partition alignments. alignmentInBytes := uint64(1048576) partitionStart := p.roundUp(existingPartitions[0].EndInBytes+1, alignmentInBytes) for index, partition := range partitions { partitionEnd := partitionStart + partition.SizeInBytes - 1 if partitionEnd >= deviceFullSizeInBytes { partitionEnd = deviceFullSizeInBytes - 1 p.logger.Info(p.logTag, "Partition %d would be larger than remaining space. Reducing size to %dB", index, partitionEnd-partitionStart) } p.logger.Info(p.logTag, "Creating partition %d with start %dB and end %dB", index, partitionStart, partitionEnd) _, _, _, err := p.cmdRunner.RunCommand( "parted", "-s", devicePath, "unit", "B", "mkpart", "primary", fmt.Sprintf("%d", partitionStart), fmt.Sprintf("%d", partitionEnd), ) if err != nil { return bosherr.WrapErrorf(err, "Partitioning disk `%s'", devicePath) } partitionStart = p.roundUp(partitionEnd+1, alignmentInBytes) } return nil }
func (creator interfaceConfigurationCreator) createMultipleInterfaceConfigurations(networks boshsettings.Networks, interfacesByMAC map[string]string) ([]StaticInterfaceConfiguration, []DHCPInterfaceConfiguration, error) { if len(interfacesByMAC) < len(networks) { return nil, nil, bosherr.Errorf("Number of network settings '%d' is greater than the number of network devices '%d'", len(networks), len(interfacesByMAC)) } for name := range networks { if mac := networks[name].Mac; mac != "" { if _, ok := interfacesByMAC[mac]; !ok { return nil, nil, bosherr.Errorf("No device found for network '%s' with MAC address '%s'", name, mac) } } } // Configure interfaces with network settings matching MAC address. // If we cannot find a network setting with a matching MAC address, configure that interface as DHCP var networkSettings boshsettings.Network var err error staticConfigs := []StaticInterfaceConfiguration{} dhcpConfigs := []DHCPInterfaceConfiguration{} for mac, ifaceName := range interfacesByMAC { networkSettings, _ = networks.NetworkForMac(mac) staticConfigs, dhcpConfigs, err = creator.createInterfaceConfiguration(staticConfigs, dhcpConfigs, ifaceName, networkSettings) if err != nil { return nil, nil, bosherr.WrapError(err, "Creating interface configuration") } } return staticConfigs, dhcpConfigs, nil }
func (r ipResolver) GetPrimaryIPv4(interfaceName string) (*gonet.IPNet, error) { addrs, err := r.ifaceToAddrsFunc(interfaceName) if err != nil { return nil, bosherr.WrapErrorf(err, "Looking up addresses for interface '%s'", interfaceName) } if len(addrs) == 0 { return nil, bosherr.Errorf("No addresses found for interface '%s'", interfaceName) } for _, addr := range addrs { ip, ok := addr.(*gonet.IPNet) if !ok { continue } // ignore ipv6 if ip.IP.To4() == nil { continue } return ip, nil } return nil, bosherr.Errorf("Failed to find primary IPv4 address for interface '%s'", interfaceName) }
func (c *agentClient) CompilePackage(packageSource agentclient.BlobRef, compiledPackageDependencies []agentclient.BlobRef) (compiledPackageRef agentclient.BlobRef, err error) { dependencies := make(map[string]BlobRef, len(compiledPackageDependencies)) for _, dependency := range compiledPackageDependencies { dependencies[dependency.Name] = BlobRef{ Name: dependency.Name, Version: dependency.Version, SHA1: dependency.SHA1, BlobstoreID: dependency.BlobstoreID, } } args := []interface{}{ packageSource.BlobstoreID, packageSource.SHA1, packageSource.Name, packageSource.Version, dependencies, } responseValue, err := c.sendAsyncTaskMessage("compile_package", args) if err != nil { return agentclient.BlobRef{}, bosherr.WrapError(err, "Sending 'compile_package' to the agent") } result, ok := responseValue["result"].(map[string]interface{}) if !ok { return agentclient.BlobRef{}, bosherr.Errorf("Unable to parse 'compile_package' response from the agent: %#v", responseValue) } sha1, ok := result["sha1"].(string) if !ok { return agentclient.BlobRef{}, bosherr.Errorf("Unable to parse 'compile_package' response from the agent: %#v", responseValue) } blobstoreID, ok := result["blobstore_id"].(string) if !ok { return agentclient.BlobRef{}, bosherr.Errorf("Unable to parse 'compile_package' response from the agent: %#v", responseValue) } compiledPackageRef = agentclient.BlobRef{ Name: packageSource.Name, Version: packageSource.Version, SHA1: sha1, BlobstoreID: blobstoreID, } return compiledPackageRef, nil }
func (p HandlerProvider) Get( platform boshplatform.Platform, dirProvider boshdir.Provider, ) (handler boshhandler.Handler, err error) { if p.handler != nil { handler = p.handler return } mbusURL, err := url.Parse(p.settingsService.GetSettings().Mbus) if err != nil { err = bosherr.WrapError(err, "Parsing handler URL") return } switch mbusURL.Scheme { case "nats": handler = NewNatsHandler(p.settingsService, yagnats.NewClient(), p.logger) case "https": handler = boshmicro.NewHTTPSHandler(mbusURL, p.logger, platform.GetFs(), dirProvider) default: err = bosherr.Errorf("Message Bus Handler with scheme %s could not be found", mbusURL.Scheme) } p.handler = handler return }
func (a MountDiskAction) Run(diskCid string) (interface{}, error) { err := a.settingsService.LoadSettings() if err != nil { return nil, bosherr.WrapError(err, "Refreshing the settings") } settings := a.settingsService.GetSettings() diskSettings, found := settings.PersistentDiskSettings(diskCid) if !found { return nil, bosherr.Errorf("Persistent disk with volume id '%s' could not be found", diskCid) } mountPoint := a.dirProvider.StoreDir() isMountPoint, err := a.mountPoints.IsMountPoint(mountPoint) if err != nil { return nil, bosherr.WrapError(err, "Checking mount point") } if isMountPoint { mountPoint = a.dirProvider.StoreMigrationDir() } err = a.diskMounter.MountPersistentDisk(diskSettings, mountPoint) if err != nil { return nil, bosherr.WrapError(err, "Mounting persistent disk") } return map[string]string{}, nil }
func (p provider) Get(name string) (Platform, error) { plat, found := p.platforms[name] if !found { return nil, bosherror.Errorf("Platform %s could not be found", name) } return plat, nil }
func (b externalBlobstore) Validate() error { if !b.runner.CommandExists(b.executable()) { return bosherr.Errorf("executable %s not found in PATH", b.executable()) } return b.writeConfigFile() }
func (s concreteV1Service) PopulateDHCPNetworks(spec V1ApplySpec, settings boshsettings.Settings) (V1ApplySpec, error) { for networkName, networkSpec := range spec.NetworkSpecs { // Skip 'local' network since for vsphere/vcloud networks // are generated incorrectly by the bosh_cli_plugin_micro/bosh-release; // can be removed with new bosh micro CLI if networkName == "local" && networkSpec.Fields["ip"] == "127.0.0.1" { continue } network, ok := settings.Networks[networkName] if !ok { return V1ApplySpec{}, bosherr.Errorf("Network '%s' is not found in settings", networkName) } if !network.IsDHCP() { continue } spec.NetworkSpecs[networkName] = networkSpec.PopulateIPInfo( network.IP, network.Netmask, network.Gateway, ) } return spec, nil }
func (p rootDevicePartitioner) GetDeviceSizeInBytes(devicePath string) (uint64, error) { p.logger.Debug(p.logTag, "Getting size of disk remaining after first partition") stdout, _, _, err := p.cmdRunner.RunCommand("parted", "-m", devicePath, "unit", "B", "print") if err != nil { return 0, bosherr.WrapErrorf(err, "Getting remaining size of `%s'", devicePath) } allLines := strings.Split(stdout, "\n") if len(allLines) < 3 { return 0, bosherr.Errorf("Getting remaining size of `%s'", devicePath) } partitionInfoLines := allLines[1:3] deviceInfo := strings.Split(partitionInfoLines[0], ":") deviceFullSizeInBytes, err := strconv.ParseUint(strings.TrimRight(deviceInfo[1], "B"), 10, 64) if err != nil { return 0, bosherr.WrapErrorf(err, "Getting remaining size of `%s'", devicePath) } firstPartitionInfo := strings.Split(partitionInfoLines[1], ":") firstPartitionEndInBytes, err := strconv.ParseUint(strings.TrimRight(firstPartitionInfo[2], "B"), 10, 64) if err != nil { return 0, bosherr.WrapErrorf(err, "Getting remaining size of `%s'", devicePath) } remainingSizeInBytes := deviceFullSizeInBytes - firstPartitionEndInBytes - 1 return remainingSizeInBytes, nil }
func (p Provider) Get(name string) (supervisor JobSupervisor, err error) { supervisor, found := p.supervisors[name] if !found { err = bosherr.Errorf("JobSupervisor %s could not be found", name) } return }
func (a UnmountDiskAction) Run(diskID string) (value interface{}, err error) { settings := a.settingsService.GetSettings() diskSettings, found := settings.PersistentDiskSettings(diskID) if !found { err = bosherr.Errorf("Persistent disk with volume id '%s' could not be found", diskID) return } didUnmount, err := a.platform.UnmountPersistentDisk(diskSettings) if err != nil { err = bosherr.WrapError(err, "Unmounting persistent disk") return } msg := fmt.Sprintf("Partition of %s is not mounted", diskSettings.Path) if didUnmount { msg = fmt.Sprintf("Unmounted partition of %s", diskSettings.Path) } type valueType struct { Message string `json:"message"` } value = valueType{Message: msg} return }
func (idpr idDevicePathResolver) GetRealDevicePath(diskSettings boshsettings.DiskSettings) (string, bool, error) { if diskSettings.ID == "" { return "", false, bosherr.Errorf("Disk ID is not set") } if len(diskSettings.ID) < 20 { return "", false, bosherr.Errorf("Disk ID is not the correct format") } err := idpr.udev.Trigger() if err != nil { return "", false, bosherr.WrapError(err, "Running udevadm trigger") } err = idpr.udev.Settle() if err != nil { return "", false, bosherr.WrapError(err, "Running udevadm settle") } stopAfter := time.Now().Add(idpr.diskWaitTimeout) found := false var realPath string diskID := diskSettings.ID[0:20] for !found { if time.Now().After(stopAfter) { return "", true, bosherr.Errorf("Timed out getting real device path for '%s'", diskID) } time.Sleep(100 * time.Millisecond) deviceIDPath := filepath.Join(string(os.PathSeparator), "dev", "disk", "by-id", fmt.Sprintf("virtio-%s", diskID)) realPath, err = idpr.fs.ReadLink(deviceIDPath) if err != nil { continue } if idpr.fs.FileExists(realPath) { found = true } } return realPath, false, nil }
func (a CancelTaskAction) Run(taskID string) (string, error) { task, found := a.taskService.FindTaskWithID(taskID) if !found { return "", bosherr.Errorf("Task with id %s could not be found", taskID) } return "canceled", task.Cancel() }
func (f concreteFactory) Create(method string) (Action, error) { action, found := f.availableActions[method] if !found { return nil, bosherr.Errorf("Could not create action with method %s", method) } return action, nil }
func (r *getStateRetryable) Attempt() (bool, error) { stateResponse, err := r.agentClient.GetState() if err != nil { return false, err } if stateResponse.JobState == "running" { return true, nil } return true, bosherr.Errorf("Received non-running job state: '%s'", stateResponse.JobState) }
func (m linuxMounter) shouldMount(partitionPath, mountPoint string) (bool, error) { mounts, err := m.mountsSearcher.SearchMounts() if err != nil { return false, bosherr.WrapError(err, "Searching mounts") } for _, mount := range mounts { switch { case mount.PartitionPath == partitionPath && mount.MountPoint == mountPoint: return false, nil case mount.PartitionPath == partitionPath && mount.MountPoint != mountPoint && partitionPath != "tmpfs": return false, bosherr.Errorf("Device %s is already mounted to %s, can't mount to %s", mount.PartitionPath, mount.MountPoint, mountPoint) case mount.MountPoint == mountPoint: return false, bosherr.Errorf("Device %s is already mounted to %s, can't mount %s", mount.PartitionPath, mount.MountPoint, partitionPath) } } return true, nil }
func (a RunScriptAction) Run(scriptName string, options map[string]interface{}) (map[string]string, error) { result := map[string]string{} currentSpec, err := a.specService.Get() if err != nil { return result, bosherr.WrapError(err, "Getting current spec") } a.logger.Info("run-script-action", "Attempting to run '%s' scripts in %d jobs", scriptName, len(currentSpec.JobSpec.JobTemplateSpecs)) scriptCount := 0 resultChannel := make(chan scriptrunner.RunScriptResult) for _, jobTemplate := range currentSpec.JobSpec.JobTemplateSpecs { script := a.scriptProvider.Get(jobTemplate.Name, scriptName) if script.Exists() { scriptCount++ go func() { resultChannel <- script.Run() }() } } var failedScripts []string var passedScripts []string for i := 0; i < scriptCount; i++ { select { case resultScript := <-resultChannel: jobName := resultScript.Tag if resultScript.Error == nil { passedScripts = append(passedScripts, jobName) result[jobName] = "executed" a.logger.Info("run-script-action", "'%s' script has successfully executed", resultScript.ScriptPath) } else { failedScripts = append(failedScripts, jobName) result[jobName] = "failed" a.logger.Error("run-script-action", "'%s' script has failed with error %s", resultScript.ScriptPath, resultScript.Error) } } } if len(failedScripts) > 0 { msg := "Failed Jobs: " + strings.Join(failedScripts, ", ") if len(passedScripts) > 0 { msg += ". Successful Jobs: " + strings.Join(passedScripts, ", ") } return result, bosherr.Errorf("%d of %d %s scripts failed. %s.", len(failedScripts), scriptCount, scriptName, msg) } return result, nil }
func (c *agentClient) Start() error { var response SimpleTaskResponse err := c.agentRequest.Send("start", []interface{}{}, &response) if err != nil { return bosherr.WrapError(err, "Starting agent services") } if response.Value != "started" { return bosherr.Errorf("Failed to start agent services with response: '%s'", response) } return nil }
func (dispatcher concreteActionDispatcher) Dispatch(req boshhandler.Request) boshhandler.Response { action, err := dispatcher.actionFactory.Create(req.Method) if err != nil { dispatcher.logger.Error(actionDispatcherLogTag, "Unknown action %s", req.Method) return boshhandler.NewExceptionResponse(bosherr.Errorf("unknown message %s", req.Method)) } if action.IsAsynchronous() { return dispatcher.dispatchAsynchronousAction(action, req) } return dispatcher.dispatchSynchronousAction(action, req) }
func (c *agentClient) sendAsyncTaskMessage(method string, arguments []interface{}) (value map[string]interface{}, err error) { var response TaskResponse err = c.agentRequest.Send(method, arguments, &response) if err != nil { return value, bosherr.WrapErrorf(err, "Sending '%s' to the agent", method) } agentTaskID, err := response.TaskID() if err != nil { return value, bosherr.WrapError(err, "Getting agent task id") } sendErrors := 0 getTaskRetryable := boshretry.NewRetryable(func() (bool, error) { var response TaskResponse err = c.agentRequest.Send("get_task", []interface{}{agentTaskID}, &response) if err != nil { sendErrors++ shouldRetry := sendErrors <= c.toleratedErrorCount err = bosherr.WrapError(err, "Sending 'get_task' to the agent") msg := fmt.Sprintf("Error occured sending get_task. Error retry %d of %d", sendErrors, c.toleratedErrorCount) c.logger.Debug(c.logTag, msg, err) return shouldRetry, err } sendErrors = 0 c.logger.Debug(c.logTag, "get_task response value: %#v", response.Value) taskState, err := response.TaskState() if err != nil { return false, bosherr.WrapError(err, "Getting task state") } if taskState != "running" { var ok bool value, ok = response.Value.(map[string]interface{}) if !ok { c.logger.Warn(c.logTag, "Unable to parse get_task response value: %#v", response.Value) } return true, nil } return true, bosherr.Errorf("Task %s is still running", method) }) getTaskRetryStrategy := boshretry.NewUnlimitedRetryStrategy(c.getTaskDelay, getTaskRetryable, c.logger) // cannot call getTaskRetryStrategy.Try in the return statement due to gccgo // execution order issues: https://code.google.com/p/go/issues/detail?id=8698&thanks=8698&ts=1410376474 err = getTaskRetryStrategy.Try() return value, err }
// TerminateNicely can be called multiple times simultaneously from different goroutines func (p *execProcess) TerminateNicely(killGracePeriod time.Duration) error { // Make sure process is being waited on for process state reaping to occur // as to avoid forcibly killing the process after killGracePeriod if p.waitCh == nil { panic("TerminateNicely() must be called after Wait()") } err := p.signalGroup(syscall.SIGTERM) if err != nil { return bosherr.WrapErrorf(err, "Sending SIGTERM to process group %d", p.pid) } terminatedCh := make(chan struct{}) stopCheckingTerminatedCh := make(chan struct{}) go func() { for p.groupExists() { select { case <-time.After(500 * time.Millisecond): // nothing to do case <-stopCheckingTerminatedCh: return } } close(terminatedCh) }() select { case <-terminatedCh: // nothing to do case <-time.After(killGracePeriod): close(stopCheckingTerminatedCh) err = p.signalGroup(syscall.SIGKILL) if err != nil { return bosherr.WrapErrorf(err, "Sending SIGKILL to process group %d", p.pid) } } // It takes some time for the process to disappear for i := 0; i < 20; i++ { if !p.groupExists() { return nil } time.Sleep(500 * time.Millisecond) } return bosherr.Errorf("Failed to kill process after grace timeout (PID %d)", p.pid) }
func (fs *osFileSystem) HomeDir(username string) (string, error) { fs.logger.Debug(fs.logTag, "Getting HomeDir for %s", username) homeDir, err := fs.runCommand(fmt.Sprintf("echo ~%s", username)) if err != nil { return "", bosherr.WrapErrorf(err, "Shelling out to get user '%s' home directory", username) } if strings.HasPrefix(homeDir, "~") { return "", bosherr.Errorf("Failed to get user '%s' home directory", username) } fs.logger.Debug(fs.logTag, "HomeDir is %s", homeDir) return homeDir, nil }
func (fs *FakeFileSystem) ReadFile(path string) ([]byte, error) { stats := fs.GetFileTestStat(path) if stats != nil { if fs.ReadFileError != nil { return nil, fs.ReadFileError } if fs.readFileErrorByPath[path] != nil { return nil, fs.readFileErrorByPath[path] } return stats.Content, nil } return nil, bosherr.Errorf("File not found: '%s'", path) }
func (s ParallelScript) summarizeErrs(passedScripts, failedScripts []string) error { if len(failedScripts) > 0 { errMsg := "Failed Jobs: " + strings.Join(failedScripts, ", ") if len(passedScripts) > 0 { errMsg += ". Successful Jobs: " + strings.Join(passedScripts, ", ") } totalRan := len(passedScripts) + len(failedScripts) return bosherr.Errorf("%d of %d %s scripts failed. %s.", len(failedScripts), totalRan, s.name, errMsg) } return nil }
func (p *CertificateVerifier) Verify(peerCertificates []*x509.Certificate) error { if len(peerCertificates) < 1 { return errors.Error("No peer certificates provided by client") } subject := peerCertificates[0].Subject for _, pattern := range p.AllowedNames { matched, err := matchName(&pattern, &subject) if err != nil { return err } if matched { return nil } } return errors.Errorf("Subject (%#v) didn't match allowed distinguished names", subject) }
func (m monitJobSupervisor) Reload() error { var currentIncarnation int oldIncarnation, err := m.getIncarnation() if err != nil { return bosherr.WrapError(err, "Getting monit incarnation") } // Monit process could be started in the same second as `monit reload` runs // so it's ideal for MaxCheckTries * DelayBetweenCheckTries to be greater than 1 sec // because monit incarnation id is just a timestamp with 1 sec resolution. for reloadI := 0; reloadI < m.reloadOptions.MaxTries; reloadI++ { // Exit code or output cannot be trusted _, _, _, err := m.runner.RunCommand("monit", "reload") if err != nil { m.logger.Error(monitJobSupervisorLogTag, "Failed to reload monit %s", err.Error()) } for checkI := 0; checkI < m.reloadOptions.MaxCheckTries; checkI++ { currentIncarnation, err = m.getIncarnation() if err != nil { return bosherr.WrapError(err, "Getting monit incarnation") } // Incarnation id can decrease or increase because // monit uses time(...) and system time can be changed if oldIncarnation != currentIncarnation { return nil } m.logger.Debug( monitJobSupervisorLogTag, "Waiting for monit to reload: before=%d after=%d", oldIncarnation, currentIncarnation, ) time.Sleep(m.reloadOptions.DelayBetweenCheckTries) } } return bosherr.Errorf( "Failed to reload monit: before=%d after=%d", oldIncarnation, currentIncarnation, ) }
func (s *SourceOptionsSlice) UnmarshalJSON(data []byte) error { var maps []map[string]interface{} err := json.Unmarshal(data, &maps) if err != nil { return bosherr.WrapError(err, "Unmarshalling sources") } for _, m := range maps { if optType, ok := m["Type"]; ok { var err error var opts SourceOptions switch { case optType == "HTTP": var o HTTPSourceOptions err, opts = mapstruc.Decode(m, &o), o case optType == "ConfigDrive": var o ConfigDriveSourceOptions err, opts = mapstruc.Decode(m, &o), o case optType == "File": var o FileSourceOptions err, opts = mapstruc.Decode(m, &o), o case optType == "CDROM": var o CDROMSourceOptions err, opts = mapstruc.Decode(m, &o), o default: err = bosherr.Errorf("Unknown source type '%s'", optType) } if err != nil { return bosherr.WrapErrorf(err, "Unmarshalling source type '%s'", optType) } *s = append(*s, opts) } else { return bosherr.Error("Missing source type") } } return nil }
func (r *requestRetryable) Attempt() (bool, error) { var err error if r.requestID == "" { r.requestID, err = r.uuidGenerator.Generate() if err != nil { return false, bosherr.WrapError(err, "Generating request uuid") } } if r.request.Body != nil && r.bodyBytes == nil { r.bodyBytes, err = ReadAndClose(r.request.Body) if err != nil { return false, bosherr.WrapError(err, "Buffering request body") } } // reset request body, because readers cannot be re-read if r.bodyBytes != nil { r.request.Body = ioutil.NopCloser(bytes.NewReader(r.bodyBytes)) } // close previous attempt's response body to prevent HTTP client resource leaks if r.response != nil { r.response.Body.Close() } r.attempt++ r.logger.Debug(r.logTag, "[requestID=%s] Requesting (attempt=%d): %s", r.requestID, r.attempt, r.formatRequest(r.request)) r.response, err = r.delegate.Do(r.request) if err != nil { r.logger.Debug(r.logTag, "[requestID=%s] Request attempt failed (attempts=%d), error: %s", r.requestID, r.attempt, err) return true, err } if r.wasSuccessful(r.response) { r.logger.Debug(r.logTag, "[requestID=%s] Request succeeded (attempts=%d), response: %s", r.requestID, r.attempt, r.formatResponse(r.response)) return false, nil } r.logger.Debug(r.logTag, "[requestID=%s] Request attempt failed (attempts=%d), response: %s", r.requestID, r.attempt, r.formatResponse(r.response)) return true, bosherr.Errorf("Request failed, response: %s", r.formatResponse(r.response)) }