<commit_before>package backend

import (
	"bytes"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/pkg/sftp"
	workerctx "github.com/travis-ci/worker/lib/context"
	"github.com/travis-ci/worker/lib/metrics"
	"golang.org/x/crypto/ssh"
	"golang.org/x/net/context"
)

type JupiterBrainProvider struct {
	client           *http.Client
	baseURL          *url.URL
	imageAliases     map[string]string
	sshKeyPath       string
	sshKeyPassphrase string
	keychainPassword string
}

type JupiterBrainInstance struct {
	payload  jupiterBrainInstancePayload
	provider *JupiterBrainProvider
}

type jupiterBrainInstancePayload struct {
	Uuid        string   `json:"uuid"`
	IpAddresses []string `json:"ip_addresses"`
	State       string   `json:"state"`
	BaseImage   string   `json:"base-image,omitempty"`
	Type        string   `json:"type,omitempty"`
}

type jupiterBrainDataResponse struct {
	Data []jupiterBrainInstancePayload `json:"data"`
}

func NewJupiterBrainProvider(config map[string]string) (*JupiterBrainProvider, error) {
	endpoint, ok := config["endpoint"]
	if !ok {
		return nil, fmt.Errorf("expected endpoint config key")
	}
	baseURL, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}

	aliasNames, ok := config["image_aliases"]
	if !ok {
		return nil, fmt.Errorf("expected image_aliases config key")
	}

	imageAliases := make(map[string]string, len(aliasNames))

	for _, aliasName := range strings.Split(aliasNames, ",") {
		imageName, ok := config[fmt.Sprintf("image_alias_%s", aliasName)]
		if !ok {
			return nil, fmt.Errorf("expected image alias %q", aliasName)
		}

		imageAliases[aliasName] = imageName
	}

	sshKeyPath, ok := config["ssh_key_path"]
	if !ok {
		return nil, fmt.Errorf("expected ssh_key_path config key")
	}

	sshKeyPassphrase, ok := config["ssh_key_passphrase"]
	if !ok {
		return nil, fmt.Errorf("expected ssh_key_passphrase config key")
	}

	keychainPassword, ok := config["keychain_password"]
	if !ok {
		return nil, fmt.Errorf("expected keychain_password config key")
	}

	return &JupiterBrainProvider{
		client:           http.DefaultClient,
		baseURL:          baseURL,
		sshKeyPath:       sshKeyPath,
		sshKeyPassphrase: sshKeyPassphrase,
		keychainPassword: keychainPassword,
	}, nil
}

func (p *JupiterBrainProvider) Start(ctx context.Context, startAttributes StartAttributes) (Instance, error) {
	u, err := p.baseURL.Parse("instances")
	if err != nil {
		return nil, err
	}

	imageName, ok := p.imageAliases[startAttributes.OsxImage]
	if !ok {
		imageName, _ = p.imageAliases["default"]
	}

	if imageName == "" {
		return nil, fmt.Errorf("no image alias for %s", startAttributes.OsxImage)
	}

	startBooting := time.Now()

	bodyPayload := map[string]map[string]string{
		"data": {
			"type":       "instances",
			"base-image": imageName,
		},
	}

	jsonBody, err := json.Marshal(bodyPayload)
	if err != nil {
		return nil, err
	}

	resp, err := p.client.Post(u.String(), "application/vnd.api+json", bytes.NewReader(jsonBody))
	if err != nil {
		return nil, err
	}
	defer io.Copy(ioutil.Discard, resp.Body)
	defer resp.Body.Close()

	if c := resp.StatusCode; c < 200 || c >= 300 {
		return nil, fmt.Errorf("expected 2xx from Jupiter Brain API, got %d", c)
	}

	var dataPayload jupiterBrainDataResponse
	err = json.NewDecoder(resp.Body).Decode(&dataPayload)
	if err != nil {
		return nil, err
	}

	payload := dataPayload.Data[0]

	instanceReady := make(chan jupiterBrainInstancePayload, 1)
	errChan := make(chan error, 1)
	go func(id string) {
		u, err := p.baseURL.Parse(fmt.Sprintf("instances/%s", url.QueryEscape(id)))
		if err != nil {
			errChan <- err
			return
		}

		req, err := http.NewRequest("GET", u.String(), nil)
		if err != nil {<commit_msg>jupiterbrain: Print API errors in log<commit_after>package backend

import (
	"bytes"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/pkg/sftp"
	workerctx "github.com/travis-ci/worker/lib/context"
	"github.com/travis-ci/worker/lib/metrics"
	"golang.org/x/crypto/ssh"
	"golang.org/x/net/context"
)

type JupiterBrainProvider struct {
	client           *http.Client
	baseURL          *url.URL
	imageAliases     map[string]string
	sshKeyPath       string
	sshKeyPassphrase string
	keychainPassword string
}

type JupiterBrainInstance struct {
	payload  jupiterBrainInstancePayload
	provider *JupiterBrainProvider
}

type jupiterBrainInstancePayload struct {
	Uuid        string   `json:"uuid"`
	IpAddresses []string `json:"ip_addresses"`
	State       string   `json:"state"`
	BaseImage   string   `json:"base-image,omitempty"`
	Type        string   `json:"type,omitempty"`
}

type jupiterBrainDataResponse struct {
	Data []jupiterBrainInstancePayload `json:"data"`
}

func NewJupiterBrainProvider(config map[string]string) (*JupiterBrainProvider, error) {
	endpoint, ok := config["endpoint"]
	if !ok {
		return nil, fmt.Errorf("expected endpoint config key")
	}
	baseURL, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}

	aliasNames, ok := config["image_aliases"]
	if !ok {
		return nil, fmt.Errorf("expected image_aliases config key")
	}

	imageAliases := make(map[string]string, len(aliasNames))

	for _, aliasName := range strings.Split(aliasNames, ",") {
		imageName, ok := config[fmt.Sprintf("image_alias_%s", aliasName)]
		if !ok {
			return nil, fmt.Errorf("expected image alias %q", aliasName)
		}

		imageAliases[aliasName] = imageName
	}

	sshKeyPath, ok := config["ssh_key_path"]
	if !ok {
		return nil, fmt.Errorf("expected ssh_key_path config key")
	}

	sshKeyPassphrase, ok := config["ssh_key_passphrase"]
	if !ok {
		return nil, fmt.Errorf("expected ssh_key_passphrase config key")
	}

	keychainPassword, ok := config["keychain_password"]
	if !ok {
		return nil, fmt.Errorf("expected keychain_password config key")
	}

	return &JupiterBrainProvider{
		client:           http.DefaultClient,
		baseURL:          baseURL,
		sshKeyPath:       sshKeyPath,
		sshKeyPassphrase: sshKeyPassphrase,
		keychainPassword: keychainPassword,
	}, nil
}

func (p *JupiterBrainProvider) Start(ctx context.Context, startAttributes StartAttributes) (Instance, error) {
	u, err := p.baseURL.Parse("instances")
	if err != nil {
		return nil, err
	}

	imageName, ok := p.imageAliases[startAttributes.OsxImage]
	if !ok {
		imageName, _ = p.imageAliases["default"]
	}

	if imageName == "" {
		return nil, fmt.Errorf("no image alias for %s", startAttributes.OsxImage)
	}

	startBooting := time.Now()

	bodyPayload := map[string]map[string]string{
		"data": {
			"type":       "instances",
			"base-image": imageName,
		},
	}

	jsonBody, err := json.Marshal(bodyPayload)
	if err != nil {
		return nil, err
	}

	resp, err := p.client.Post(u.String(), "application/vnd.api+json", bytes.NewReader(jsonBody))
	if err != nil {
		return nil, err
	}
	defer io.Copy(ioutil.Discard, resp.Body)
	defer resp.Body.Close()

	if c := resp.StatusCode; c < 200 || c >= 300 {
		body, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("expected 2xx from Jupiter Brain API, got %d (error: %s)", c, body)
	}

	var dataPayload jupiterBrainDataResponse
	err = json.NewDecoder(resp.Body).Decode(&dataPayload)
	if err != nil {
		return nil, err
	}

	payload := dataPayload.Data[0]

	instanceReady := make(chan jupiterBrainInstancePayload, 1)
	errChan := make(chan error, 1)
	go func(id string) {
		u, err := p.baseURL.Parse(fmt.Sprintf("instances/%s", url.QueryEscape(id)))
		if err != nil {
			errChan <- err
			return
		}

		req, err := http.NewRequest("GET", u.String(), nil)
		if err != nil {
{'fraction_non_alphanumeric': 0.13438683557529058, 'fraction_numerical': 0.003395585738539898, 'mean_word_length': 4.101932045303132, 'pattern_counts': {'":': 6, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5187667', 'n_tokens_mistral': 2845, 'n_tokens_neox': 2656, 'n_words': 610}