<commit_before>package publisher

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"time"

	"github.com/elastic/libbeat/common"
	"github.com/elastic/libbeat/logp"
	"github.com/elastic/libbeat/outputs"
	"github.com/elastic/libbeat/outputs/elasticsearch"
	"github.com/elastic/libbeat/outputs/fileout"
	"github.com/elastic/libbeat/outputs/redis"
	"github.com/nranchev/go-libGeoIP"
)

type PublisherType struct {
	name           string
	tags           []string
	disabled       bool
	Index          string
	Output         []outputs.OutputInterface
	TopologyOutput outputs.OutputInterface
	IgnoreOutgoing bool
	GeoLite        *libgeo.GeoIP

	RefreshTopologyTimer <-chan time.Time
	Queue                chan common.MapStr
}

type ShipperConfig struct {
	Name                  string
	Refresh_topology_freq int
	Ignore_outgoing       bool
	Topology_expire       int
	Tags                  []string
	Geoip                 common.Geoip
}

var Publisher PublisherType

type Topology struct {
	Name string `json:"name"`
	Ip   string `json:"ip"`
}

var EnabledOutputPlugins map[outputs.OutputPlugin]outputs.OutputInterface = map[outputs.OutputPlugin]outputs.OutputInterface{
	outputs.RedisOutput:         new(redis.RedisOutput),
	outputs.ElasticsearchOutput: new(elasticsearch.ElasticsearchOutput),
	outputs.FileOutput:          new(fileout.FileOutput),
}

func PrintPublishEvent(event common.MapStr) {
	json, err := json.MarshalIndent(event, "", "  ")
	if err != nil {
		logp.Err("json.Marshal: %s", err)
	} else {
		logp.Debug("publish", "Publish: %s", string(json))
	}
}

func (publisher *PublisherType) GetServerName(ip string) string {
	// in case the IP is localhost, return current shipper name
	islocal, err := common.IsLoopback(ip)
	if err != nil {
		logp.Err("Parsing IP %s fails with: %s", ip, err)
		return ""
	} else {
		if islocal {
			return publisher.name
		}
	}
	// find the shipper with the desired IP
	if publisher.TopologyOutput != nil {
		return publisher.TopologyOutput.GetNameByIP(ip)
	} else {
		return ""
	}
}

func (publisher *PublisherType) publishFromQueue() {
	for mapstr := range publisher.Queue {
		err := publisher.publishEvent(mapstr)
		if err != nil {
			logp.Err("Publishing failed: %v", err)
		}
	}
}

func (publisher *PublisherType) publishEvent(event common.MapStr) error {

	// the timestamp is mandatory
	ts, ok := event["timestamp"].(common.Time)
	if !ok {
		return errors.New("Missing 'timestamp' field from event.")
	}

	// the count is mandatory
	err := event.EnsureCountField()
	if err != nil {
		return err
	}

	// the type is mandatory
	_, ok = event["type"].(string)
	if !ok {
		return errors.New("Missing 'type' field from event.")
	}

	var src_server, dst_server string
	src, ok := event["src"].(*common.Endpoint)
	if ok {
		src_server = publisher.GetServerName(src.Ip)
		event["client_ip"] = src.Ip
		event["client_port"] = src.Port
		event["client_proc"] = src.Proc
		event["client_server"] = src_server
		delete(event, "src")
	}
	dst, ok := event["dst"].(*common.Endpoint)
	if ok {
		dst_server = publisher.GetServerName(dst.Ip)
		event["ip"] = dst.Ip
		event["port"] = dst.Port
		event["proc"] = dst.Proc
		event["server"] = dst_server
		delete(event, "dst")
	}

	if publisher.IgnoreOutgoing && dst_server != "" &&
		dst_server != publisher.name {
		// duplicated transaction -> ignore it
		logp.Debug("publish", "Ignore duplicated transaction on %s: %s -> %s", publisher.name, src_server, dst_server)
		return nil
	}

	event["shipper"] = publisher.name
	if len(publisher.tags) > 0 {
		event["tags"] = publisher.tags
	}

	if publisher.GeoLite != nil {
		real_ip, exists := event["real_ip"]
		if exists && len(real_ip.(string)) > 0 {
			loc := publisher.GeoLite.GetLocationByIP(real_ip.(string))
			if loc != nil && loc.Latitude != 0 && loc.Longitude != 0 {
				event["client_location"] = fmt.Sprintf("%f, %f", loc.Latitude, loc.Longitude)
			}
		} else {
			if len(src_server) == 0 && src != nil { // only for external IP addresses
				loc := publisher.GeoLite.GetLocationByIP(src.Ip)
				if loc != nil && loc.Latitude != 0 && loc.Longitude != 0 {
					event["client_location"] = fmt.Sprintf("%f, %f", loc.Latitude, loc.Longitude)
				}
			}
		}
	}

	if logp.IsDebug("publish") {
		PrintPublishEvent(event)
	}

	// add transaction
	has_error := false
	if !publisher.disabled {
		for i := 0; i < len(publisher.Output); i++ {
			err := publisher.Output[i].PublishEvent(time.Time(ts), event)
			if err != nil {
				logp.Err("Fail to publish event type on output %s: %s", publisher.Output, err)
				has_error = true
			}
		}
	}

	if has_error {
		return errors.New("Fail to publish event")
	}
	return nil
}

func (publisher *PublisherType) UpdateTopologyPeriodically() {
	for _ = range publisher.RefreshTopologyTimer {
		publisher.PublishTopology()
	}
}

func (publisher *PublisherType) PublishTopology(params ...string) error {

	var localAddrs []string = params

	if len(params) == 0 {
		addrs, err := common.LocalIpAddrsAsStrings(false)
		if err != nil {
			logp.Err("Getting local IP addresses fails with: %s", err)
			return err
		}
		localAddrs = addrs
	}

	if publisher.TopologyOutput != nil {
		logp.Debug("publish", "Add topology entry for %s: %s", publisher.name, localAddrs)

		err := publisher.TopologyOutput.PublishIPs(publisher.name, localAddrs)
		if err != nil {
			return err
		}
	}

	return nil
}

func (publisher *PublisherType) Init(publishDisabled bool,
	outputs map[string]outputs.MothershipConfig, shipper ShipperConfig) error {
	var err error
	publisher.IgnoreOutgoing = shipper.Ignore_outgoing

	publisher.disabled = publishDisabled
	if publisher.disabled {
		logp.Info("Dry run mode. All output types except the file based one are disabled.")
	}

	publisher.GeoLite = common.LoadGeoIPData(shipper.Geoip)

	for outputId, plugin := range EnabledOutputPlugins {
		outputName := outputId.String()
		output, exists := outputs[outputName]
		if exists && output.Enabled && !publisher.disabled {
			err := plugin.Init(output, shipper.Topology_expire)
			if err != nil {
				logp.Err("Fail to initialize %s plugin as output: %s", outputName, err)
				return err
			}
			publisher.Output = append(publisher.Output, plugin)

			if output.Save_topology {
				if publisher.TopologyOutput != nil {
					logp.Err("Multiple outputs defined to store topology. Please add save_topology = true option only for one output.")
					return errors.New("Multiple outputs defined to store topology")
				}
				publisher.TopologyOutput = plugin
				logp.Info("Using %s to store the topology", outputName)
			}
		}
	}

	if !publisher.disabled {
		if len(publisher.Output) == 0 {
			logp.Info("No outputs are defined. Please define one under the shipper->output section.")
			return errors.New("No outputs are defined. Please define one under the shipper->output section.")
		}

		if publisher.TopologyOutput == nil {
			logp.Warn("No output is defined to store the topology. The server fields might not be filled.")
		}
	}

	publisher.name = shipper.Name
	if len(publisher.name) == 0 {
		// use the hostname
		publisher.name, err = os.Hostname()
		if err != nil {
			return err
		}

		logp.Info("No shipper name configured, using hostname '%s'", publisher.name)
	}

	publisher.tags = shipper.Tags

	if !publisher.disabled && publisher.TopologyOutput != nil {
		RefreshTopologyFreq := 10 * time.Second
		if shipper.Refresh_topology_freq != 0 {
			RefreshTopologyFreq = time.Duration(shipper.Refresh_topology_freq) * time.Second
		}
		publisher.RefreshTopologyTimer = time.Tick(RefreshTopologyFreq)
		logp.Info("Topology map refreshed every %s", RefreshTopologyFreq)

		// register shipper and its public IP addresses
		err = publisher.PublishTopology()
		if err != nil {
			logp.Err("Failed to publish topology: %s", err)
			return err
		}

		// update topology periodically
		go publisher.UpdateTopologyPeriodically()
	}

	publisher.Queue = make(chan common.MapStr, 1000)
	go publisher.publishFromQueue()

	return nil
}
<commit_msg>Improved error message<commit_after>package publisher

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"time"

	"github.com/elastic/libbeat/common"
	"github.com/elastic/libbeat/logp"
	"github.com/elastic/libbeat/outputs"
	"github.com/elastic/libbeat/outputs/elasticsearch"
	"github.com/elastic/libbeat/outputs/fileout"
	"github.com/elastic/libbeat/outputs/redis"
	"github.com/nranchev/go-libGeoIP"
)

type PublisherType struct {
	name           string
	tags           []string
	disabled       bool
	Index          string
	Output         []outputs.OutputInterface
	TopologyOutput outputs.OutputInterface
	IgnoreOutgoing bool
	GeoLite        *libgeo.GeoIP

	RefreshTopologyTimer <-chan time.Time
	Queue                chan common.MapStr
}

type ShipperConfig struct {
	Name                  string
	Refresh_topology_freq int
	Ignore_outgoing       bool
	Topology_expire       int
	Tags                  []string
	Geoip                 common.Geoip
}

var Publisher PublisherType

type Topology struct {
	Name string `json:"name"`
	Ip   string `json:"ip"`
}

var EnabledOutputPlugins map[outputs.OutputPlugin]outputs.OutputInterface = map[outputs.OutputPlugin]outputs.OutputInterface{
	outputs.RedisOutput:         new(redis.RedisOutput),
	outputs.ElasticsearchOutput: new(elasticsearch.ElasticsearchOutput),
	outputs.FileOutput:          new(fileout.FileOutput),
}

func PrintPublishEvent(event common.MapStr) {
	json, err := json.MarshalIndent(event, "", "  ")
	if err != nil {
		logp.Err("json.Marshal: %s", err)
	} else {
		logp.Debug("publish", "Publish: %s", string(json))
	}
}

func (publisher *PublisherType) GetServerName(ip string) string {
	// in case the IP is localhost, return current shipper name
	islocal, err := common.IsLoopback(ip)
	if err != nil {
		logp.Err("Parsing IP %s fails with: %s", ip, err)
		return ""
	} else {
		if islocal {
			return publisher.name
		}
	}
	// find the shipper with the desired IP
	if publisher.TopologyOutput != nil {
		return publisher.TopologyOutput.GetNameByIP(ip)
	} else {
		return ""
	}
}

func (publisher *PublisherType) publishFromQueue() {
	for mapstr := range publisher.Queue {
		err := publisher.publishEvent(mapstr)
		if err != nil {
			logp.Err("Publishing failed: %v", err)
		}
	}
}

func (publisher *PublisherType) publishEvent(event common.MapStr) error {

	// the timestamp is mandatory
	ts, ok := event["timestamp"].(common.Time)
	if !ok {
		return errors.New("Missing 'timestamp' field from event.")
	}

	// the count is mandatory
	err := event.EnsureCountField()
	if err != nil {
		return err
	}

	// the type is mandatory
	_, ok = event["type"].(string)
	if !ok {
		return errors.New("Missing 'type' field from event.")
	}

	var src_server, dst_server string
	src, ok := event["src"].(*common.Endpoint)
	if ok {
		src_server = publisher.GetServerName(src.Ip)
		event["client_ip"] = src.Ip
		event["client_port"] = src.Port
		event["client_proc"] = src.Proc
		event["client_server"] = src_server
		delete(event, "src")
	}
	dst, ok := event["dst"].(*common.Endpoint)
	if ok {
		dst_server = publisher.GetServerName(dst.Ip)
		event["ip"] = dst.Ip
		event["port"] = dst.Port
		event["proc"] = dst.Proc
		event["server"] = dst_server
		delete(event, "dst")
	}

	if publisher.IgnoreOutgoing && dst_server != "" &&
		dst_server != publisher.name {
		// duplicated transaction -> ignore it
		logp.Debug("publish", "Ignore duplicated transaction on %s: %s -> %s", publisher.name, src_server, dst_server)
		return nil
	}

	event["shipper"] = publisher.name
	if len(publisher.tags) > 0 {
		event["tags"] = publisher.tags
	}

	if publisher.GeoLite != nil {
		real_ip, exists := event["real_ip"]
		if exists && len(real_ip.(string)) > 0 {
			loc := publisher.GeoLite.GetLocationByIP(real_ip.(string))
			if loc != nil && loc.Latitude != 0 && loc.Longitude != 0 {
				event["client_location"] = fmt.Sprintf("%f, %f", loc.Latitude, loc.Longitude)
			}
		} else {
			if len(src_server) == 0 && src != nil { // only for external IP addresses
				loc := publisher.GeoLite.GetLocationByIP(src.Ip)
				if loc != nil && loc.Latitude != 0 && loc.Longitude != 0 {
					event["client_location"] = fmt.Sprintf("%f, %f", loc.Latitude, loc.Longitude)
				}
			}
		}
	}

	if logp.IsDebug("publish") {
		PrintPublishEvent(event)
	}

	// add transaction
	has_error := false
	if !publisher.disabled {
		for i := 0; i < len(publisher.Output); i++ {
			err := publisher.Output[i].PublishEvent(time.Time(ts), event)
			if err != nil {
				logp.Err("Fail to publish event type on output %s: %v", publisher.Output[i], err)
				has_error = true
			}
		}
	}

	if has_error {
		return errors.New("Fail to publish event")
	}
	return nil
}

func (publisher *PublisherType) UpdateTopologyPeriodically() {
	for _ = range publisher.RefreshTopologyTimer {
		publisher.PublishTopology()
	}
}

func (publisher *PublisherType) PublishTopology(params ...string) error {

	var localAddrs []string = params

	if len(params) == 0 {
		addrs, err := common.LocalIpAddrsAsStrings(false)
		if err != nil {
			logp.Err("Getting local IP addresses fails with: %s", err)
			return err
		}
		localAddrs = addrs
	}

	if publisher.TopologyOutput != nil {
		logp.Debug("publish", "Add topology entry for %s: %s", publisher.name, localAddrs)

		err := publisher.TopologyOutput.PublishIPs(publisher.name, localAddrs)
		if err != nil {
			return err
		}
	}

	return nil
}

func (publisher *PublisherType) Init(publishDisabled bool,
	outputs map[string]outputs.MothershipConfig, shipper ShipperConfig) error {
	var err error
	publisher.IgnoreOutgoing = shipper.Ignore_outgoing

	publisher.disabled = publishDisabled
	if publisher.disabled {
		logp.Info("Dry run mode. All output types except the file based one are disabled.")
	}

	publisher.GeoLite = common.LoadGeoIPData(shipper.Geoip)

	for outputId, plugin := range EnabledOutputPlugins {
		outputName := outputId.String()
		output, exists := outputs[outputName]
		if exists && output.Enabled && !publisher.disabled {
			err := plugin.Init(output, shipper.Topology_expire)
			if err != nil {
				logp.Err("Fail to initialize %s plugin as output: %s", outputName, err)
				return err
			}
			publisher.Output = append(publisher.Output, plugin)

			if output.Save_topology {
				if publisher.TopologyOutput != nil {
					logp.Err("Multiple outputs defined to store topology. Please add save_topology = true option only for one output.")
					return errors.New("Multiple outputs defined to store topology")
				}
				publisher.TopologyOutput = plugin
				logp.Info("Using %s to store the topology", outputName)
			}
		}
	}

	if !publisher.disabled {
		if len(publisher.Output) == 0 {
			logp.Info("No outputs are defined. Please define one under the shipper->output section.")
			return errors.New("No outputs are defined. Please define one under the shipper->output section.")
		}

		if publisher.TopologyOutput == nil {
			logp.Warn("No output is defined to store the topology. The server fields might not be filled.")
		}
	}

	publisher.name = shipper.Name
	if len(publisher.name) == 0 {
		// use the hostname
		publisher.name, err = os.Hostname()
		if err != nil {
			return err
		}

		logp.Info("No shipper name configured, using hostname '%s'", publisher.name)
	}

	publisher.tags = shipper.Tags

	if !publisher.disabled && publisher.TopologyOutput != nil {
		RefreshTopologyFreq := 10 * time.Second
		if shipper.Refresh_topology_freq != 0 {
			RefreshTopologyFreq = time.Duration(shipper.Refresh_topology_freq) * time.Second
		}
		publisher.RefreshTopologyTimer = time.Tick(RefreshTopologyFreq)
		logp.Info("Topology map refreshed every %s", RefreshTopologyFreq)

		// register shipper and its public IP addresses
		err = publisher.PublishTopology()
		if err != nil {
			logp.Err("Failed to publish topology: %s", err)
			return err
		}

		// update topology periodically
		go publisher.UpdateTopologyPeriodically()
	}

	publisher.Queue = make(chan common.MapStr, 1000)
	go publisher.publishFromQueue()

	return nil
}
<|endoftext|>


<commit_before>package search_test

import (
	"testing"

	"github.com/wasanx25/sreq/search"
)

func TestNew(t *testing.T) {
	actual := search.New("testK", "testS")
	if actual.Keyword != "testK" {
		t.Errorf("expected=%q, got=%q", "testK", actual.Keyword)
	}

	if actual.Sort != "testS" {
		t.Errorf("expected=%q, got=%q", "testS", actual.Sort)
	}
}

func TestGetURL(t *testing.T) {
	s := search.New("testK", "testS")
	expectedURL := "https://qiita.com/search?page=0&q=testK&sort=testS"
	actual := s.GetURL()

	if actual != expectedURL {
		t.Errorf("expected=%q, got=%q", expectedURL, actual)
	}
}

func TestNextPage(t *testing.T) {
	s := search.New("testK", "testS")
	s.NextPage()

	if s.Pagenation != 1 {
		t.Errorf("expected=%q, got=%q", 1, s.Pagenation)
	}
}

func TestExec(t *testing.T) {
	s := search.New("testK", "testS")
	t.Run("return content", func(t *testing.T) {
		actualC, actualE := s.Exec("https://qiita.com")
		var expectedContents []*search.Content
		var expectedError error

		for i, content := range actualC {
			if content != expectedContents[i] {
				t.Errorf("expected=%q, got=%q", expectedContents[i], content)
			}
		}

		if actualE != expectedError {
			t.Errorf("expected=%q, got=%q", expectedError, actualE)
		}
	})
}
<commit_msg>Add TODO comment<commit_after>package search_test

import (
	"testing"

	"github.com/wasanx25/sreq/search"
)

func TestNew(t *testing.T) {
	actual := search.New("testK", "testS")
	if actual.Keyword != "testK" {
		t.Errorf("expected=%q, got=%q", "testK", actual.Keyword)
	}

	if actual.Sort != "testS" {
		t.Errorf("expected=%q, got=%q", "testS", actual.Sort)
	}
}

func TestGetURL(t *testing.T) {
	s := search.New("testK", "testS")
	expectedURL := "https://qiita.com/search?page=0&q=testK&sort=testS"
	actual := s.GetURL()

	if actual != expectedURL {
		t.Errorf("expected=%q, got=%q", expectedURL, actual)
	}
}

func TestNextPage(t *testing.T) {
	s := search.New("testK", "testS")
	s.NextPage()

	if s.Pagenation != 1 {
		t.Errorf("expected=%q, got=%q", 1, s.Pagenation)
	}
}

func TestExec(t *testing.T) {
	s := search.New("testK", "testS")
	t.Run("return content", func(t *testing.T) {
		// TODO: use dummy URL that running TCP
		actualC, actualE := s.Exec("https://qiita.com")
		var expectedContents []*search.Content
		var expectedError error

		for i, content := range actualC {
			if content != expectedContents[i] {
				t.Errorf("expected=%q, got=%q", expectedContents[i], content)
			}
		}

		if actualE != expectedError {
			t.Errorf("expected=%q, got=%q", expectedError, actualE)
		}
	})
}
<|endoftext|>


<commit_before>package pushbullet

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/url"
	"strings"

	"github.com/mitsuse/bullet/pushbullet/responses"
)

// Post the information of file and get the information of upload destination.
func (pb *Pushbullet) PostUploadRequest(name, mime string) (*responses.Upload, error) {
	res, err := pb.postUploadRequest(name, mime)
	if err != nil {
		return nil, err
	}

	// TODO: Return an error value with human friendly message.
	if res.StatusCode != 200 {
		return nil, errors.New(res.Status)
	}

	decoder := json.NewDecoder(res.Body)

	var upload *responses.Upload
	if err := decoder.Decode(&upload); err != nil {
		return nil, err
	}

	return upload, nil
}

func (pb *Pushbullet) postUploadRequest(name, mime string) (*http.Response, error) {
	values := url.Values{
		"file_name": []string{name},
		"file_type": []string{mime},
	}
	reader := strings.NewReader(values.Encode())

	req, err := http.NewRequest("POST", ENDPOINT_UPLOADREQ, reader)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.SetBasicAuth(pb.token, "")

	// TODO: Set the timeout.
	client := &http.Client{}

	return client.Do(req)
}

// Upload a file to S3 specified with the response of PostUploadRequest.
func Upload(upload *responses.Upload, reader io.Reader) error {
	req, err := createMultipartReq(upload, reader)
	if err != nil {
		return err
	}

	// TODO: Set the timeout.
	client := &http.Client{}

	res, err := client.Do(req)
	if err != nil {
		return err
	}

	// TODO: Return an error value with human friendly message.
	if res.StatusCode < 200 || 300 <= res.StatusCode {
		return errors.New(res.Status)
	}

	return nil
}

func createMultipartReq(upload *responses.Upload, reader io.Reader) (*http.Request, error) {
	dest := upload.Data

	buffer := &bytes.Buffer{}

	writer := newMultipartWriter(buffer)

	writer.WriteField("awsaccesskeyid", dest.AwsAccessKeyId)
	writer.WriteField("acl", dest.Acl)
	writer.WriteField("key", dest.Key)
	writer.WriteField("signature", dest.Signature)
	writer.WriteField("policy", dest.Policy)
	writer.WriteField("content-type", dest.ContentType)

	if err := writer.Error(); err != nil {
		return nil, err
	}

	fw, err := writer.CreateFormFile("file", upload.FileName)
	if err != nil {
		return nil, err
	}

	if _, err := io.Copy(fw, reader); err != nil {
		return nil, err
	}

	if err := writer.Close(); err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", upload.UploadUrl, buffer)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", writer.FormDataContentType())

	return req, nil
}
<commit_msg>Rename: createMultipartReq -> createUploadReq<commit_after>package pushbullet

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/url"
	"strings"

	"github.com/mitsuse/bullet/pushbullet/responses"
)

// Post the information of file and get the information of upload destination.
func (pb *Pushbullet) PostUploadRequest(name, mime string) (*responses.Upload, error) {
	res, err := pb.postUploadRequest(name, mime)
	if err != nil {
		return nil, err
	}

	// TODO: Return an error value with human friendly message.
	if res.StatusCode != 200 {
		return nil, errors.New(res.Status)
	}

	decoder := json.NewDecoder(res.Body)

	var upload *responses.Upload
	if err := decoder.Decode(&upload); err != nil {
		return nil, err
	}

	return upload, nil
}

func (pb *Pushbullet) postUploadRequest(name, mime string) (*http.Response, error) {
	values := url.Values{
		"file_name": []string{name},
		"file_type": []string{mime},
	}
	reader := strings.NewReader(values.Encode())

	req, err := http.NewRequest("POST", ENDPOINT_UPLOADREQ, reader)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.SetBasicAuth(pb.token, "")

	// TODO: Set the timeout.
	client := &http.Client{}

	return client.Do(req)
}

// Upload a file to S3 specified with the response of PostUploadRequest.
func Upload(upload *responses.Upload, reader io.Reader) error {
	req, err := createUploadReq(upload, reader)
	if err != nil {
		return err
	}

	// TODO: Set the timeout.
	client := &http.Client{}

	res, err := client.Do(req)
	if err != nil {
		return err
	}

	// TODO: Return an error value with human friendly message.
	if res.StatusCode < 200 || 300 <= res.StatusCode {
		return errors.New(res.Status)
	}

	return nil
}

func createUploadReq(upload *responses.Upload, reader io.Reader) (*http.Request, error) {
	dest := upload.Data

	buffer := &bytes.Buffer{}

	writer := newMultipartWriter(buffer)

	writer.WriteField("awsaccesskeyid", dest.AwsAccessKeyId)
	writer.WriteField("acl", dest.Acl)
	writer.WriteField("key", dest.Key)
	writer.WriteField("signature", dest.Signature)
	writer.WriteField("policy", dest.Policy)
	writer.WriteField("content-type", dest.ContentType)

	if err := writer.Error(); err != nil {
		return nil, err
	}

	fw, err := writer.CreateFormFile("file", upload.FileName)
	if err != nil {
		return nil, err
	}

	if _, err := io.Copy(fw, reader); err != nil {
		return nil, err
	}

	if err := writer.Close(); err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", upload.UploadUrl, buffer)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", writer.FormDataContentType())

	return req, nil
}
<|endoftext|>


<commit_before>package middleware

import (
	"html/template"
	"net/http"
	"os"
	"path"
	"path/filepath"

	"github.com/admpub/log"

	"github.com/webx-top/echo"
)

var ListDirTemplate = `<!doctype html>

        <meta charset="UTF-8">
        <title>{{.file}}</title>
        <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
        <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport" />
        <link href="/favicon.ico" rel="shortcut icon">

		{{range $k, $d := .dirs}}

{{$d.Name}}{{if $d.IsDir}}/{{end}}

		{{end}}

`

type (
	StaticOptions struct {
		// Skipper defines a function to skip middleware.
		Skipper echo.Skipper `json:"-"`

		Path     string          `json:"path"` //UrlPath
		Root     string          `json:"root"`
		Fallback []string        `json:"fallback"`
		Index    string          `json:"index"`
		Browse   bool            `json:"browse"`
		Template string          `json:"template"`
		Debug    bool            `json:"debug"`
		FS       http.FileSystem `json:"-"`

		open   func(string) (http.File, error)
		render func(echo.Context, interface{}) error
	}
)

func Static(options ...*StaticOptions) echo.MiddlewareFunc {
	// Default options
	opts := new(StaticOptions)
	if len(options) > 0 {
		opts = options[0]
	}
	return opts.Init().Middleware()
}

func (s *StaticOptions) Init() *StaticOptions {
	if s.Skipper == nil {
		s.Skipper = echo.DefaultSkipper
	}
	s.Root, _ = filepath.Abs(s.Root)
	for index, fallback := range s.Fallback {
		s.Fallback[index], _ = filepath.Abs(fallback)
	}
	if len(s.Path) > 0 && s.Path[0] != '/' {
		s.Path = `/` + s.Path
	}
	if s.Debug {
		log.GetLogger("echo").Debugf("Static: %v\t-> %v", s.Path, s.Root)
	}
	return s
}

func (s *StaticOptions) getOpener() func(file string) (http.File, error) {
	if s.open != nil {
		return s.open
	}
	if s.FS != nil {
		s.open = s.FS.Open
	} else {
		s.open = func(name string) (http.File, error) {
			fp, err := os.Open(name)
			return fp, err
		}
	}
	return s.open
}

func (s *StaticOptions) getRender() func(c echo.Context, data interface{}) error {
	if s.render != nil {
		return s.render
	}
	if !s.Browse {
		return nil
	}
	if len(s.Template) > 0 {
		s.render = func(c echo.Context, data interface{}) error {
			return c.Render(s.Template, data)
		}
	} else {
		t := template.New(s.Template)
		_, e := t.Parse(ListDirTemplate)
		if e != nil {
			panic(e)
		}
		s.render = func(c echo.Context, data interface{}) error {
			w := c.Response()
			w.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
			return t.Execute(w, data)
		}
	}
	return s.render
}

func (s *StaticOptions) findFile(c echo.Context, root string, hasIndex bool, file string, render func(echo.Context, interface{}) error, opener func(string) (http.File, error)) error {
	absFile := filepath.Join(root, file)
	fp, err := opener(absFile)
	if err != nil {
		return echo.ErrNotFound
	}
	defer fp.Close()
	fi, err := fp.Stat()
	if err != nil {
		return echo.ErrNotFound
	}
	if fi.IsDir() {
		if hasIndex {
			fp.Close()
			// Index file
			indexFile := filepath.Join(absFile, s.Index)
			fp, err = opener(indexFile)
			if err != nil {
				return echo.ErrNotFound
			}
			fi, err = fp.Stat()
			if err != nil || fi.IsDir() {
				if s.Browse {
					return listDirByCustomFS(absFile, file, c, render, opener)
				}
				return echo.ErrNotFound
			}
			absFile = indexFile
		} else {
			if s.Browse {
				return listDirByCustomFS(absFile, file, c, render, opener)
			}
			return echo.ErrNotFound
		}
	}
	return c.ServeContent(fp, fi.Name(), fi.ModTime())
}

func (s *StaticOptions) Middleware() echo.MiddlewareFunc {
	render := s.getRender()
	opener := s.getOpener()
	hasIndex := len(s.Index) > 0
	return func(next echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			if s.Skipper(c) {
				return next.Handle(c)
			}
			file := c.Request().URL().Path()
			length := len(s.Path)
			if len(file) < length || file[0:length] != s.Path {
				return next.Handle(c)
			}
			file = file[length:]
			file = path.Clean(file)
			err := s.findFile(c, s.Root, hasIndex, file, render, opener)
			if err == nil {
				return err
			}
			if err == echo.ErrNotFound {
				if len(s.Fallback) == 0 {
					return err
				}
				for _, fallback := range s.Fallback {
					err = s.findFile(c, fallback, hasIndex, file, render, opener)
					if err == nil {
						return err
					}
				}
			}
			return err
		})
	}
}

func listDirByCustomFS(absFile string, file string, c echo.Context, render func(echo.Context, interface{}) error, opener func(string) (http.File, error)) error {
	d, err := opener(file)
	if err != nil {
		return echo.ErrNotFound
	}
	defer d.Close()
	dirs, err := d.Readdir(-1)
	if err != nil {
		return echo.ErrNotFound
	}

	return render(c, map[string]interface{}{
		`file`: file,
		`dirs`: dirs,
	})
}
<commit_msg>update<commit_after>package middleware

import (
	"html/template"
	"net/http"
	"os"
	"path"
	"path/filepath"

	"github.com/admpub/log"

	"github.com/webx-top/echo"
)

var ListDirTemplate = `<!doctype html>

        <meta charset="UTF-8">
        <title>{{.file}}</title>
        <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
        <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport" />
        <link href="/favicon.ico" rel="shortcut icon">

		{{range $k, $d := .dirs}}

{{$d.Name}}{{if $d.IsDir}}/{{end}}

		{{end}}

`

type (
	StaticOptions struct {
		// Skipper defines a function to skip middleware.
		Skipper echo.Skipper `json:"-"`

		Path     string          `json:"path"` //UrlPath
		Root     string          `json:"root"`
		Fallback []string        `json:"fallback"`
		Index    string          `json:"index"`
		Browse   bool            `json:"browse"`
		Template string          `json:"template"`
		Debug    bool            `json:"debug"`
		FS       http.FileSystem `json:"-"`

		open   func(string) (http.File, error)
		render func(echo.Context, interface{}) error
	}
)

func Static(options ...*StaticOptions) echo.MiddlewareFunc {
	// Default options
	opts := new(StaticOptions)
	if len(options) > 0 {
		opts = options[0]
	}
	return opts.Init().Middleware()
}

func (s *StaticOptions) Init() *StaticOptions {
	if s.Skipper == nil {
		s.Skipper = echo.DefaultSkipper
	}
	s.Root, _ = filepath.Abs(s.Root)
	for index, fallback := range s.Fallback {
		s.Fallback[index], _ = filepath.Abs(fallback)
	}
	if len(s.Path) > 0 && s.Path[0] != '/' {
		s.Path = `/` + s.Path
	}
	if s.Debug {
		log.GetLogger("echo").Debugf("Static: %v\t-> %v", s.Path, s.Root)
	}
	return s
}

func (s *StaticOptions) AddFallback(fallback string) *StaticOptions {
	fallback, _ = filepath.Abs(fallback)
	s.Fallback = append(s.Fallback, fallback)
	return s
}

func (s *StaticOptions) getOpener() func(file string) (http.File, error) {
	if s.open != nil {
		return s.open
	}
	if s.FS != nil {
		s.open = s.FS.Open
	} else {
		s.open = func(name string) (http.File, error) {
			fp, err := os.Open(name)
			return fp, err
		}
	}
	return s.open
}

func (s *StaticOptions) getRender() func(c echo.Context, data interface{}) error {
	if s.render != nil {
		return s.render
	}
	if !s.Browse {
		return nil
	}
	if len(s.Template) > 0 {
		s.render = func(c echo.Context, data interface{}) error {
			return c.Render(s.Template, data)
		}
	} else {
		t := template.New(s.Template)
		_, e := t.Parse(ListDirTemplate)
		if e != nil {
			panic(e)
		}
		s.render = func(c echo.Context, data interface{}) error {
			w := c.Response()
			w.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
			return t.Execute(w, data)
		}
	}
	return s.render
}

func (s *StaticOptions) findFile(c echo.Context, root string, hasIndex bool, file string, render func(echo.Context, interface{}) error, opener func(string) (http.File, error)) error {
	absFile := filepath.Join(root, file)
	fp, err := opener(absFile)
	if err != nil {
		return echo.ErrNotFound
	}
	defer fp.Close()
	fi, err := fp.Stat()
	if err != nil {
		return echo.ErrNotFound
	}
	if fi.IsDir() {
		if hasIndex {
			fp.Close()
			// Index file
			indexFile := filepath.Join(absFile, s.Index)
			fp, err = opener(indexFile)
			if err != nil {
				return echo.ErrNotFound
			}
			fi, err = fp.Stat()
			if err != nil || fi.IsDir() {
				if s.Browse {
					return listDirByCustomFS(absFile, file, c, render, opener)
				}
				return echo.ErrNotFound
			}
			absFile = indexFile
		} else {
			if s.Browse {
				return listDirByCustomFS(absFile, file, c, render, opener)
			}
			return echo.ErrNotFound
		}
	}
	return c.ServeContent(fp, fi.Name(), fi.ModTime())
}

func (s *StaticOptions) Middleware() echo.MiddlewareFunc {
	render := s.getRender()
	opener := s.getOpener()
	hasIndex := len(s.Index) > 0
	return func(next echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			if s.Skipper(c) {
				return next.Handle(c)
			}
			file := c.Request().URL().Path()
			length := len(s.Path)
			if len(file) < length || file[0:length] != s.Path {
				return next.Handle(c)
			}
			file = file[length:]
			file = path.Clean(file)
			err := s.findFile(c, s.Root, hasIndex, file, render, opener)
			if err == nil {
				return err
			}
			if err == echo.ErrNotFound {
				if len(s.Fallback) == 0 {
					return err
				}
				for _, fallback := range s.Fallback {
					err = s.findFile(c, fallback, hasIndex, file, render, opener)
					if err == nil {
						return err
					}
				}
			}
			return err
		})
	}
}

func listDirByCustomFS(absFile string, file string, c echo.Context, render func(echo.Context, interface{}) error, opener func(string) (http.File, error)) error {
	d, err := opener(file)
	if err != nil {
		return echo.ErrNotFound
	}
	defer d.Close()
	dirs, err := d.Readdir(-1)
	if err != nil {
		return echo.ErrNotFound
	}

	return render(c, map[string]interface{}{
		`file`: file,
		`dirs`: dirs,
	})
}
<|endoftext|>


<commit_before>package rep

import (
	"encoding/json"
	"errors"
	"net/url"
	"strconv"

	"code.cloudfoundry.org/bbs/models"
	"code.cloudfoundry.org/executor"
)

const (
	LifecycleTag  = "lifecycle"
	ResultFileTag = "result-file"
	DomainTag     = "domain"

	TaskLifecycle = "task"
	LRPLifecycle  = "lrp"

	ProcessGuidTag  = "process-guid"
	InstanceGuidTag = "instance-guid"
	ProcessIndexTag = "process-index"

	VolumeDriversTag = "volume-drivers"
	PlacementTagsTag = "placement-tags"
)

var (
	ErrContainerMissingTags = errors.New("container is missing tags")
	ErrInvalidProcessIndex  = errors.New("container does not have a valid process index")
)

func ActualLRPKeyFromTags(tags executor.Tags) (*models.ActualLRPKey, error) {
	if tags == nil {
		return &models.ActualLRPKey{}, ErrContainerMissingTags
	}

	processIndex, err := strconv.Atoi(tags[ProcessIndexTag])
	if err != nil {
		return &models.ActualLRPKey{}, ErrInvalidProcessIndex
	}

	actualLRPKey := models.NewActualLRPKey(
		tags[ProcessGuidTag],
		int32(processIndex),
		tags[DomainTag],
	)

	err = actualLRPKey.Validate()
	if err != nil {
		return &models.ActualLRPKey{}, err
	}

	return &actualLRPKey, nil
}

func ActualLRPInstanceKeyFromContainer(container executor.Container, cellID string) (*models.ActualLRPInstanceKey, error) {
	if container.Tags == nil {
		return &models.ActualLRPInstanceKey{}, ErrContainerMissingTags
	}

	actualLRPInstanceKey := models.NewActualLRPInstanceKey(
		container.Tags[InstanceGuidTag],
		cellID,
	)

	err := actualLRPInstanceKey.Validate()
	if err != nil {
		return &models.ActualLRPInstanceKey{}, err
	}

	return &actualLRPInstanceKey, nil
}

func ActualLRPNetInfoFromContainer(container executor.Container) (*models.ActualLRPNetInfo, error) {
	ports := []*models.PortMapping{}

	for _, port := range container.Ports {
		// todo: do not include proxy ports
		ports = append(ports, models.NewPortMappingWithTLSProxy(
			uint32(port.HostPort),
			uint32(port.ContainerPort),
			uint32(port.HostTLSProxyPort),
			uint32(port.ContainerTLSProxyPort),
		))
	}

	actualLRPNetInfo := models.NewActualLRPNetInfo(container.ExternalIP, container.InternalIP, ports...)

	err := actualLRPNetInfo.Validate()
	if err != nil {
		return nil, err
	}

	return &actualLRPNetInfo, nil
}

func LRPContainerGuid(processGuid, instanceGuid string) string {
	return instanceGuid
}

func NewRunRequestFromDesiredLRP(
	containerGuid string,
	desiredLRP *models.DesiredLRP,
	lrpKey *models.ActualLRPKey,
	lrpInstanceKey *models.ActualLRPInstanceKey,
) (executor.RunRequest, error) {
	diskScope, err := diskScopeForRootFS(desiredLRP.RootFs)
	if err != nil {
		return executor.RunRequest{}, err
	}

	mounts, err := convertVolumeMounts(desiredLRP.VolumeMounts)
	if err != nil {
		return executor.RunRequest{}, err
	}

	runInfo := executor.RunInfo{
		CPUWeight: uint(desiredLRP.CpuWeight),
		DiskScope: diskScope,
		Ports:     ConvertPortMappings(desiredLRP.Ports),
		LogConfig: executor.LogConfig{
			Guid:       desiredLRP.LogGuid,
			Index:      int(lrpKey.Index),
			SourceName: desiredLRP.LogSource,
		},

		MetricsConfig: executor.MetricsConfig{
			Guid:  desiredLRP.MetricsGuid,
			Index: int(lrpKey.Index),
		},
		StartTimeoutMs:     uint(desiredLRP.StartTimeoutMs),
		Privileged:         desiredLRP.Privileged,
		CachedDependencies: ConvertCachedDependencies(desiredLRP.CachedDependencies),
		Setup:              desiredLRP.Setup,
		Action:             desiredLRP.Action,
		Monitor:            desiredLRP.Monitor,
		CheckDefinition:    desiredLRP.CheckDefinition,
		EgressRules:        desiredLRP.EgressRules,
		Env: append([]executor.EnvironmentVariable{
			{Name: "INSTANCE_GUID", Value: lrpInstanceKey.InstanceGuid},
			{Name: "INSTANCE_INDEX", Value: strconv.Itoa(int(lrpKey.Index))},
			{Name: "CF_INSTANCE_GUID", Value: lrpInstanceKey.InstanceGuid},
			{Name: "CF_INSTANCE_INDEX", Value: strconv.Itoa(int(lrpKey.Index))},
		}, executor.EnvironmentVariablesFromModel(desiredLRP.EnvironmentVariables)...),
		TrustedSystemCertificatesPath: desiredLRP.TrustedSystemCertificatesPath,
		VolumeMounts:                  mounts,
		Network:                       convertNetwork(desiredLRP.Network),
		CertificateProperties:         convertCertificateProperties(desiredLRP.CertificateProperties),
		ImageUsername:                 desiredLRP.ImageUsername,
		ImagePassword:                 desiredLRP.ImagePassword,
		EnableContainerProxy:          true,
	}
	tags := executor.Tags{}
	return executor.NewRunRequest(containerGuid, &runInfo, tags), nil
}

func NewRunRequestFromTask(task *models.Task) (executor.RunRequest, error) {
	diskScope, err := diskScopeForRootFS(task.RootFs)
	if err != nil {
		return executor.RunRequest{}, err
	}

	mounts, err := convertVolumeMounts(task.VolumeMounts)
	if err != nil {
		return executor.RunRequest{}, err
	}

	tags := executor.Tags{
		ResultFileTag: task.ResultFile,
	}
	runInfo := executor.RunInfo{
		DiskScope:  diskScope,
		CPUWeight:  uint(task.CpuWeight),
		Privileged: task.Privileged,
		LogConfig: executor.LogConfig{
			Guid:       task.LogGuid,
			SourceName: task.LogSource,
		},
		MetricsConfig: executor.MetricsConfig{
			Guid: task.MetricsGuid,
		},
		CachedDependencies:            ConvertCachedDependencies(task.CachedDependencies),
		Action:                        task.Action,
		Env:                           executor.EnvironmentVariablesFromModel(task.EnvironmentVariables),
		EgressRules:                   task.EgressRules,
		TrustedSystemCertificatesPath: task.TrustedSystemCertificatesPath,
		VolumeMounts:                  mounts,
		Network:                       convertNetwork(task.Network),
		CertificateProperties:         convertCertificateProperties(task.CertificateProperties),
		ImageUsername:                 task.ImageUsername,
		ImagePassword:                 task.ImagePassword,
		EnableContainerProxy:          false,
	}
	return executor.NewRunRequest(task.TaskGuid, &runInfo, tags), nil
}

func ConvertCachedDependencies(modelDeps []*models.CachedDependency) []executor.CachedDependency {
	execDeps := make([]executor.CachedDependency, len(modelDeps))
	for i := range modelDeps {
		execDeps[i] = ConvertCachedDependency(modelDeps[i])
	}
	return execDeps
}

func ConvertCachedDependency(modelDep *models.CachedDependency) executor.CachedDependency {
	return executor.CachedDependency{
		Name:              modelDep.Name,
		From:              modelDep.From,
		To:                modelDep.To,
		CacheKey:          modelDep.CacheKey,
		LogSource:         modelDep.LogSource,
		ChecksumValue:     modelDep.ChecksumValue,
		ChecksumAlgorithm: modelDep.ChecksumAlgorithm,
	}
}

func convertVolumeMounts(volumeMounts []*models.VolumeMount) ([]executor.VolumeMount, error) {
	execMnts := make([]executor.VolumeMount, len(volumeMounts))
	for i := range volumeMounts {
		var err error
		execMnts[i], err = convertVolumeMount(volumeMounts[i])
		if err != nil {
			return nil, err
		}
	}
	return execMnts, nil
}

func convertVolumeMount(volumeMnt *models.VolumeMount) (executor.VolumeMount, error) {
	var config map[string]interface{}

	if len(volumeMnt.Shared.MountConfig) > 0 {
		err := json.Unmarshal([]byte(volumeMnt.Shared.MountConfig), &config)
		if err != nil {
			return executor.VolumeMount{}, err
		}
	}

	var mode executor.BindMountMode
	switch volumeMnt.Mode {
	case "r":
		mode = executor.BindMountModeRO
	case "rw":
		mode = executor.BindMountModeRW
	default:
		return executor.VolumeMount{}, errors.New("unrecognized volume mount mode")
	}

	return executor.VolumeMount{
		Driver:        volumeMnt.Driver,
		VolumeId:      volumeMnt.Shared.VolumeId,
		ContainerPath: volumeMnt.ContainerDir,
		Mode:          mode,
		Config:        config,
	}, nil
}

func convertNetwork(network *models.Network) *executor.Network {
	if network == nil {
		return nil
	}

	return &executor.Network{
		Properties: network.Properties,
	}
}

func convertCertificateProperties(props *models.CertificateProperties) executor.CertificateProperties {
	if props == nil {
		return executor.CertificateProperties{}
	}

	return executor.CertificateProperties{
		OrganizationalUnit: props.OrganizationalUnit,
	}
}

func ConvertPortMappings(containerPorts []uint32) []executor.PortMapping {
	out := []executor.PortMapping{}
	for _, port := range containerPorts {
		out = append(out, executor.PortMapping{
			ContainerPort: uint16(port),
		})
	}

	return out
}

func IsPreloadedRootFS(rootFS string) (bool, error) {
	preloaded := false

	url, err := url.Parse(rootFS)
	if err != nil {
		return false, err
	}

	if url.Scheme == models.PreloadedRootFSScheme || url.Scheme == models.PreloadedOCIRootFSScheme {
		preloaded = true
	}

	return preloaded, nil
}

func diskScopeForRootFS(rootFS string) (executor.DiskLimitScope, error) {
	preloaded, err := IsPreloadedRootFS(rootFS)
	if err != nil {
		return executor.ExclusiveDiskLimit, err
	}

	if preloaded {
		return executor.ExclusiveDiskLimit, nil
	}
	return executor.TotalDiskLimit, nil
}
<commit_msg>Remove an incorrect comment<commit_after>package rep

import (
	"encoding/json"
	"errors"
	"net/url"
	"strconv"

	"code.cloudfoundry.org/bbs/models"
	"code.cloudfoundry.org/executor"
)

const (
	LifecycleTag  = "lifecycle"
	ResultFileTag = "result-file"
	DomainTag     = "domain"

	TaskLifecycle = "task"
	LRPLifecycle  = "lrp"

	ProcessGuidTag  = "process-guid"
	InstanceGuidTag = "instance-guid"
	ProcessIndexTag = "process-index"

	VolumeDriversTag = "volume-drivers"
	PlacementTagsTag = "placement-tags"
)

var (
	ErrContainerMissingTags = errors.New("container is missing tags")
	ErrInvalidProcessIndex  = errors.New("container does not have a valid process index")
)

func ActualLRPKeyFromTags(tags executor.Tags) (*models.ActualLRPKey, error) {
	if tags == nil {
		return &models.ActualLRPKey{}, ErrContainerMissingTags
	}

	processIndex, err := strconv.Atoi(tags[ProcessIndexTag])
	if err != nil {
		return &models.ActualLRPKey{}, ErrInvalidProcessIndex
	}

	actualLRPKey := models.NewActualLRPKey(
		tags[ProcessGuidTag],
		int32(processIndex),
		tags[DomainTag],
	)

	err = actualLRPKey.Validate()
	if err != nil {
		return &models.ActualLRPKey{}, err
	}

	return &actualLRPKey, nil
}

func ActualLRPInstanceKeyFromContainer(container executor.Container, cellID string) (*models.ActualLRPInstanceKey, error) {
	if container.Tags == nil {
		return &models.ActualLRPInstanceKey{}, ErrContainerMissingTags
	}

	actualLRPInstanceKey := models.NewActualLRPInstanceKey(
		container.Tags[InstanceGuidTag],
		cellID,
	)

	err := actualLRPInstanceKey.Validate()
	if err != nil {
		return &models.ActualLRPInstanceKey{}, err
	}

	return &actualLRPInstanceKey, nil
}

func ActualLRPNetInfoFromContainer(container executor.Container) (*models.ActualLRPNetInfo, error) {
	ports := []*models.PortMapping{}

	for _, port := range container.Ports {
		ports = append(ports, models.NewPortMappingWithTLSProxy(
			uint32(port.HostPort),
			uint32(port.ContainerPort),
			uint32(port.HostTLSProxyPort),
			uint32(port.ContainerTLSProxyPort),
		))
	}

	actualLRPNetInfo := models.NewActualLRPNetInfo(container.ExternalIP, container.InternalIP, ports...)

	err := actualLRPNetInfo.Validate()
	if err != nil {
		return nil, err
	}

	return &actualLRPNetInfo, nil
}

func LRPContainerGuid(processGuid, instanceGuid string) string {
	return instanceGuid
}

func NewRunRequestFromDesiredLRP(
	containerGuid string,
	desiredLRP *models.DesiredLRP,
	lrpKey *models.ActualLRPKey,
	lrpInstanceKey *models.ActualLRPInstanceKey,
) (executor.RunRequest, error) {
	diskScope, err := diskScopeForRootFS(desiredLRP.RootFs)
	if err != nil {
		return executor.RunRequest{}, err
	}

	mounts, err := convertVolumeMounts(desiredLRP.VolumeMounts)
	if err != nil {
		return executor.RunRequest{}, err
	}

	runInfo := executor.RunInfo{
		CPUWeight: uint(desiredLRP.CpuWeight),
		DiskScope: diskScope,
		Ports:     ConvertPortMappings(desiredLRP.Ports),
		LogConfig: executor.LogConfig{
			Guid:       desiredLRP.LogGuid,
			Index:      int(lrpKey.Index),
			SourceName: desiredLRP.LogSource,
		},

		MetricsConfig: executor.MetricsConfig{
			Guid:  desiredLRP.MetricsGuid,
			Index: int(lrpKey.Index),
		},
		StartTimeoutMs:     uint(desiredLRP.StartTimeoutMs),
		Privileged:         desiredLRP.Privileged,
		CachedDependencies: ConvertCachedDependencies(desiredLRP.CachedDependencies),
		Setup:              desiredLRP.Setup,
		Action:             desiredLRP.Action,
		Monitor:            desiredLRP.Monitor,
		CheckDefinition:    desiredLRP.CheckDefinition,
		EgressRules:        desiredLRP.EgressRules,
		Env: append([]executor.EnvironmentVariable{
			{Name: "INSTANCE_GUID", Value: lrpInstanceKey.InstanceGuid},
			{Name: "INSTANCE_INDEX", Value: strconv.Itoa(int(lrpKey.Index))},
			{Name: "CF_INSTANCE_GUID", Value: lrpInstanceKey.InstanceGuid},
			{Name: "CF_INSTANCE_INDEX", Value: strconv.Itoa(int(lrpKey.Index))},
		}, executor.EnvironmentVariablesFromModel(desiredLRP.EnvironmentVariables)...),
		TrustedSystemCertificatesPath: desiredLRP.TrustedSystemCertificatesPath,
		VolumeMounts:                  mounts,
		Network:                       convertNetwork(desiredLRP.Network),
		CertificateProperties:         convertCertificateProperties(desiredLRP.CertificateProperties),
		ImageUsername:                 desiredLRP.ImageUsername,
		ImagePassword:                 desiredLRP.ImagePassword,
		EnableContainerProxy:          true,
	}
	tags := executor.Tags{}
	return executor.NewRunRequest(containerGuid, &runInfo, tags), nil
}

func NewRunRequestFromTask(task *models.Task) (executor.RunRequest, error) {
	diskScope, err := diskScopeForRootFS(task.RootFs)
	if err != nil {
		return executor.RunRequest{}, err
	}

	mounts, err := convertVolumeMounts(task.VolumeMounts)
	if err != nil {
		return executor.RunRequest{}, err
	}

	tags := executor.Tags{
		ResultFileTag: task.ResultFile,
	}
	runInfo := executor.RunInfo{
		DiskScope:  diskScope,
		CPUWeight:  uint(task.CpuWeight),
		Privileged: task.Privileged,
		LogConfig: executor.LogConfig{
			Guid:       task.LogGuid,
			SourceName: task.LogSource,
		},
		MetricsConfig: executor.MetricsConfig{
			Guid: task.MetricsGuid,
		},
		CachedDependencies:            ConvertCachedDependencies(task.CachedDependencies),
		Action:                        task.Action,
		Env:                           executor.EnvironmentVariablesFromModel(task.EnvironmentVariables),
		EgressRules:                   task.EgressRules,
		TrustedSystemCertificatesPath: task.TrustedSystemCertificatesPath,
		VolumeMounts:                  mounts,
		Network:                       convertNetwork(task.Network),
		CertificateProperties:         convertCertificateProperties(task.CertificateProperties),
		ImageUsername:                 task.ImageUsername,
		ImagePassword:                 task.ImagePassword,
		EnableContainerProxy:          false,
	}
	return executor.NewRunRequest(task.TaskGuid, &runInfo, tags), nil
}

func ConvertCachedDependencies(modelDeps []*models.CachedDependency) []executor.CachedDependency {
	execDeps := make([]executor.CachedDependency, len(modelDeps))
	for i := range modelDeps {
		execDeps[i] = ConvertCachedDependency(modelDeps[i])
	}
	return execDeps
}

func ConvertCachedDependency(modelDep *models.CachedDependency) executor.CachedDependency {
	return executor.CachedDependency{
		Name:              modelDep.Name,
		From:              modelDep.From,
		To:                modelDep.To,
		CacheKey:          modelDep.CacheKey,
		LogSource:         modelDep.LogSource,
		ChecksumValue:     modelDep.ChecksumValue,
		ChecksumAlgorithm: modelDep.ChecksumAlgorithm,
	}
}

func convertVolumeMounts(volumeMounts []*models.VolumeMount) ([]executor.VolumeMount, error) {
	execMnts := make([]executor.VolumeMount, len(volumeMounts))
	for i := range volumeMounts {
		var err error
		execMnts[i], err = convertVolumeMount(volumeMounts[i])
		if err != nil {
			return nil, err
		}
	}
	return execMnts, nil
}

func convertVolumeMount(volumeMnt *models.VolumeMount) (executor.VolumeMount, error) {
	var config map[string]interface{}

	if len(volumeMnt.Shared.MountConfig) > 0 {
		err := json.Unmarshal([]byte(volumeMnt.Shared.MountConfig), &config)
		if err != nil {
			return executor.VolumeMount{}, err
		}
	}

	var mode executor.BindMountMode
	switch volumeMnt.Mode {
	case "r":
		mode = executor.BindMountModeRO
	case "rw":
		mode = executor.BindMountModeRW
	default:
		return executor.VolumeMount{}, errors.New("unrecognized volume mount mode")
	}

	return executor.VolumeMount{
		Driver:        volumeMnt.Driver,
		VolumeId:      volumeMnt.Shared.VolumeId,
		ContainerPath: volumeMnt.ContainerDir,
		Mode:          mode,
		Config:        config,
	}, nil
}

func convertNetwork(network *models.Network) *executor.Network {
	if network == nil {
		return nil
	}

	return &executor.Network{
		Properties: network.Properties,
	}
}

func convertCertificateProperties(props *models.CertificateProperties) executor.CertificateProperties {
	if props == nil {
		return executor.CertificateProperties{}
	}

	return executor.CertificateProperties{
		OrganizationalUnit: props.OrganizationalUnit,
	}
}

func ConvertPortMappings(containerPorts []uint32) []executor.PortMapping {
	out := []executor.PortMapping{}
	for _, port := range containerPorts {
		out = append(out, executor.PortMapping{
			ContainerPort: uint16(port),
		})
	}

	return out
}

func IsPreloadedRootFS(rootFS string) (bool, error) {
	preloaded := false

	url, err := url.Parse(rootFS)
	if err != nil {
		return false, err
	}

	if url.Scheme == models.PreloadedRootFSScheme || url.Scheme == models.PreloadedOCIRootFSScheme {
		preloaded = true
	}

	return preloaded, nil
}

func diskScopeForRootFS(rootFS string) (executor.DiskLimitScope, error) {
	preloaded, err := IsPreloadedRootFS(rootFS)
	if err != nil {
		return executor.ExclusiveDiskLimit, err
	}

	if preloaded {
		return executor.ExclusiveDiskLimit, nil
	}
	return executor.TotalDiskLimit, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var cmdPush = &Command{
	Run:   runPush,
	Usage: "push path name",
	Short: "Deploy single artifact from a local directory",
	Long: `
Deploy single artifact from a local directory

Examples:

  force push connectedApps name
`,
}

var pxml = `<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>%s</members>
        <name>%s</name>
    </types>
    <version>29.0</version>
</Package>`

func runPush(cmd *Command, args []string) {
	if len(args) == 0 {
		cmd.printUsage()
		return
	}

	wd, _ := os.Getwd()
	root := filepath.Join(wd, "metadata")
	//if len(args) == 1 {
	//	root, _ = filepath.Abs(args[0])
	//}

	if _, err := os.Stat(filepath.Join(root, "package.xml")); os.IsNotExist(err) {
        root = filepath.Join(wd, "src")
        if _, err := os.Stat(filepath.Join(root, "package.xml")); os.IsNotExist(err) {
            ErrorAndExit("Must specify a directory that contains metadata files")
        }
	}

	if _, err := os.Stat(filepath.Join(root, args[0])); os.IsNotExist(err) {
		ErrorAndExit("Folder " + args[0] + " not found, must specify a metadata folder")
	}

	objType := args[0]

	switch args[0] {
	case "objects":
		objType = "CustomObject"
	case "flexipages":
		objType = "FlexiPage"
	case "tabs":
		objType = "CustomTab"
	case "components":
		objType = "ApexComponent"
	case "pages":
		objType = "ApexPage"
	case "classes":
		objType = "ApexClass"
	default:
		ErrorAndExit("That folder type is not supported")
	}

	found := false
	err := filepath.Walk(filepath.Join(root, args[0]), func(path string, f os.FileInfo, err error) error {
		if f.Mode().IsRegular() {
            fname := strings.ToLower(f.Name())
            fname = strings.TrimSuffix(fname, filepath.Ext(fname))
			if strings.ToLower(fname) == strings.ToLower(args[1]) {
				found = true
			}
		}
		return nil
	})
	if err != nil {
		ErrorAndExit(err.Error())
	}
	if !found {
		ErrorAndExit("Could not find " + args[1] + " in " + args[0])
	}

	force, _ := ActiveForce()
	files := make(ForceMetadataFiles)

	err = os.Rename(filepath.Join(root, "package.xml"), filepath.Join(root, "package.copy.xml"))

	if err := ioutil.WriteFile(filepath.Join(root, "package.xml"), []byte(fmt.Sprintf(pxml, args[1], objType)), 0644); err != nil {
		ErrorAndExit(err.Error())
	}
	if err != nil {
		ErrorAndExit(err.Error())
	}

	err = filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
		if f.Mode().IsRegular() {
            fname := strings.ToLower(f.Name())
            fname = strings.TrimSuffix(fname, filepath.Ext(fname))
			if fname == strings.ToLower(args[1]) {
				data, err := ioutil.ReadFile(path)
				if err != nil {
					ErrorAndExit(err.Error())
				}
				files[strings.Replace(path, fmt.Sprintf("%s/", root), "", -1)] = data

                path += "-meta.xml"
				data, err = ioutil.ReadFile(path)
				if err != nil {
					ErrorAndExit(err.Error())
				}
				files[strings.Replace(path, fmt.Sprintf("%s/", root), "", -1)] = data
			}
            if f.Name() == "package.xml" {
				data, err := ioutil.ReadFile(path)
				if err != nil {
					ErrorAndExit(err.Error())
				}
				files[strings.Replace(path, fmt.Sprintf("%s/", root), "", -1)] = data
            }
		}
		return nil
	})
	if err != nil {
		ErrorAndExit(err.Error())
	}

	var DeploymentOptions ForceDeployOptions
	successes, problems, err := force.Metadata.Deploy(files, DeploymentOptions)
	if err != nil {
		ErrorAndExit(err.Error())
	}
	fmt.Printf("\nFailures - %d\n", len(problems))
	for _, problem := range problems {
		if problem.FullName == "" {
			fmt.Println(problem.Problem)
		} else {
			fmt.Printf("ERROR with %s:\n %s\n", problem.FullName, problem.Problem)
		}
	}

	fmt.Printf("\nSuccesses - %d\n", len(successes))
	for _, success := range successes {
		if success.FullName != "package.xml" {
			verb := "unchanged"
			if success.Changed {
				verb = "changed"
			} else if success.Deleted {
				verb = "deleted"
			} else if success.Created {
				verb = "created"
			}
			fmt.Printf("%s\n\tstatus: %s\n\tid=%s\n", success.FullName, verb, success.Id)
		}
	}

	fmt.Printf("Pushed %s to Force.com\n", args[1])
}
<commit_msg>New push by file path<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var cmdPush = &Command{
	Run:   runPush,
	Usage: "push path name",
	Short: "Deploy single artifact from a local directory",
	Long: `
Deploy single artifact from a local directory

Examples:

  force push connectedApps name
`,
}

var pxml = `<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>%s</members>
        <name>%s</name>
    </types>
    <version>29.0</version>
</Package>`

type metapath struct {
    path string
    name string
}

var metapaths = []metapath{
    metapath{"classes"      , "ApexClass"},
    metapath{"objects"      , "CustomObject"},
    metapath{"tabs"         , "CustomTab"},
    metapath{"flexipages"   , "FlexiPage"},
    metapath{"components"   , "ApexComponent"},
    metapath{"triggers"     , "ApexTrigger"},
    metapath{"pages"        , "ApexPage"},
}

func getPathForMeta(metaname string) string {
    for _, mp := range metapaths {
        if mp.name == metaname {
            return mp.path
        }
    }

    // Unknown, so use metaname
    return metaname
}

func getMetaForPath(path string) string {
    for _, mp := range metapaths {
        if mp.path == path {
            return mp.name
        }
    }

    // Unknown, so use path
    return path
}

func runPush(cmd *Command, args []string) {
	if len(args) == 0 {
		cmd.printUsage()
		return
	}

    if len(args) == 1 {
        pushByPath(args[0])
    }

	if len(args) == 2 {
        pushByName(args)
    }
}

func pushByName(args []string) {
	wd, _ := os.Getwd()
	root := filepath.Join(wd, "metadata")
	//if len(args) == 1 {
	//	root, _ = filepath.Abs(args[0])
	//}

	if _, err := os.Stat(filepath.Join(root, "package.xml")); os.IsNotExist(err) {
        root = filepath.Join(wd, "src")
        if _, err := os.Stat(filepath.Join(root, "package.xml")); os.IsNotExist(err) {
            ErrorAndExit("Must specify a directory that contains metadata files")
        }
	}

	if _, err := os.Stat(filepath.Join(root, args[0])); os.IsNotExist(err) {
		ErrorAndExit("Folder " + args[0] + " not found, must specify a metadata folder")
	}

    objType := getMetaForPath(args[0])

	found := false
	err := filepath.Walk(filepath.Join(root, args[0]), func(path string, f os.FileInfo, err error) error {
		if f.Mode().IsRegular() {
            fname := strings.ToLower(f.Name())
            fname = strings.TrimSuffix(fname, filepath.Ext(fname))
			if strings.ToLower(fname) == strings.ToLower(args[1]) {
				found = true
			}
		}
		return nil
	})
	if err != nil {
		ErrorAndExit(err.Error())
	}
	if !found {
		ErrorAndExit("Could not find " + args[1] + " in " + args[0])
	}

	files := make(ForceMetadataFiles)

	err = os.Rename(filepath.Join(root, "package.xml"), filepath.Join(root, "package.copy.xml"))

	if err := ioutil.WriteFile(filepath.Join(root, "package.xml"), []byte(fmt.Sprintf(pxml, args[1], objType)), 0644); err != nil {
		ErrorAndExit(err.Error())
	}
	if err != nil {
		ErrorAndExit(err.Error())
	}

	err = filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
		if f.Mode().IsRegular() {
            fname := strings.ToLower(f.Name())
            fname = strings.TrimSuffix(fname, filepath.Ext(fname))
			if fname == strings.ToLower(args[1]) {
				data, err := ioutil.ReadFile(path)
				if err != nil {
					ErrorAndExit(err.Error())
				}
				files[strings.Replace(path, fmt.Sprintf("%s/", root), "", -1)] = data

                path += "-meta.xml"
				data, err = ioutil.ReadFile(path)
				if err != nil {
					ErrorAndExit(err.Error())
				}
				files[strings.Replace(path, fmt.Sprintf("%s/", root), "", -1)] = data
			}
            if f.Name() == "package.xml" {
				data, err := ioutil.ReadFile(path)
				if err != nil {
					ErrorAndExit(err.Error())
				}
				files[strings.Replace(path, fmt.Sprintf("%s/", root), "", -1)] = data
            }
		}
		return nil
	})
	if err != nil {
		ErrorAndExit(err.Error())
	}

    deployFiles(files)

	fmt.Printf("Pushed %s to Force.com\n", args[1])
}

// Push metadata object by path to a file
func pushByPath(fpath string) {
    fpath, err := filepath.Abs(fpath)
    if err != nil {
        ErrorAndExit("Cound not find " + fpath)
    }
    if _, err := os.Stat(fpath); err != nil {
        ErrorAndExit("Cound not open " + fpath)
    }

    hasMeta := true
    fname := filepath.Base(fpath)
    fname = strings.TrimSuffix(fname, filepath.Ext(fname))
    fdir := filepath.Dir(fpath)
    typePath := filepath.Base(fdir)
    srcDir := filepath.Dir(fdir)
    metaType := getMetaForPath(typePath)
    // Should be present since we worked back to srcDir
    frel, _ := filepath.Rel(srcDir, fpath)

    // Try to find meta file
    fmeta := fpath + "-meta.xml"
    fmetarel := ""
    if _, err := os.Stat(fmeta); err != nil {
        if os.IsNotExist(err) {
            hasMeta = false
        } else {
            ErrorAndExit("Cound not open " + fmeta)
        }
    } else {
        // Should be present since we worked back to srcDir
        fmetarel, _ = filepath.Rel(srcDir, fmeta)
    }

    // DEBUG
    // fmt.Println("Dir: " + fdir)
    // fmt.Println("fname: " + fname)
    // fmt.Println("Dir end: " + typePath)
    // fmt.Println("srcDir: " + srcDir)
    // fmt.Println("Type: " + metaType)
    // fmt.Println("relPath: " + frel)

	files := make(ForceMetadataFiles)

    fdata, err := ioutil.ReadFile(fpath)
    files[frel] = fdata
    // IF META EXISTS
    if hasMeta {
        fdata, err = ioutil.ReadFile(fmeta)
        files[fmetarel] = fdata
    }

    files["package.xml"] = []byte(fmt.Sprintf(pxml, fname, metaType))

    deployFiles(files)
}

func deployFiles(files ForceMetadataFiles) {
	force, _ := ActiveForce()
	var DeploymentOptions ForceDeployOptions
	successes, problems, err := force.Metadata.Deploy(files, DeploymentOptions)
	if err != nil {
		ErrorAndExit(err.Error())
	}
	fmt.Printf("\nFailures - %d\n", len(problems))
	for _, problem := range problems {
		if problem.FullName == "" {
			fmt.Println(problem.Problem)
		} else {
			fmt.Printf("ERROR with %s:\n %s\n", problem.FullName, problem.Problem)
		}
	}

	fmt.Printf("\nSuccesses - %d\n", len(successes))
	for _, success := range successes {
		if success.FullName != "package.xml" {
			verb := "unchanged"
			if success.Changed {
				verb = "changed"
			} else if success.Deleted {
				verb = "deleted"
			} else if success.Created {
				verb = "created"
			}
			fmt.Printf("%s\n\tstatus: %s\n\tid=%s\n", success.FullName, verb, success.Id)
		}
	}

}
<|endoftext|>


<commit_before>// +build docker

package errors_test

import (
	"fmt"

	apperrors "github.com/vardius/go-api-boilerplate/pkg/errors"
)

func ExampleNew() {
	err := apperrors.New("example")

	fmt.Printf("%s", err)

	// Output:
	// example
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:12
}

func ExampleWrap() {
	subErr := apperrors.New("example")
	err := apperrors.Wrap(subErr)

	fmt.Printf("%s", err)

	// Output:
	// example
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:23
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:22
}

func ExampleWrap_second() {
	originalErr := fmt.Errorf("original")
	wrappedErr := apperrors.Wrap(originalErr)

	err := fmt.Errorf("test2: %w", wrappedErr)

	fmt.Printf("%s", err)

	// Output:
	// test2: original
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:35
}

func ExampleWrap_third() {
	subErr := apperrors.Wrap(fmt.Errorf("first"))
	err := apperrors.Wrap(subErr)

	deeper := apperrors.Wrap(fmt.Errorf("second: %w", err))
	all := apperrors.Wrap(deeper)

	fmt.Printf("%s\n\n", all)

	// Output:
	// second: first
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:48
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:47
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:51
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:50
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:48
	// /home/runner/work/go-api-boilerplate/pkg/errors/example_test.go:47
}
<commit_msg>Fix example test output<commit_after>// +build docker

package errors_test

import (
	"fmt"

	apperrors "github.com/vardius/go-api-boilerplate/go-api-boilerplate/pkg/errors"
)

func ExampleNew() {
	err := apperrors.New("example")

	fmt.Printf("%s", err)

	// Output:
	// example
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:12
}

func ExampleWrap() {
	subErr := apperrors.New("example")
	err := apperrors.Wrap(subErr)

	fmt.Printf("%s", err)

	// Output:
	// example
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:23
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:22
}

func ExampleWrap_second() {
	originalErr := fmt.Errorf("original")
	wrappedErr := apperrors.Wrap(originalErr)

	err := fmt.Errorf("test2: %w", wrappedErr)

	fmt.Printf("%s", err)

	// Output:
	// test2: original
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:35
}

func ExampleWrap_third() {
	subErr := apperrors.Wrap(fmt.Errorf("first"))
	err := apperrors.Wrap(subErr)

	deeper := apperrors.Wrap(fmt.Errorf("second: %w", err))
	all := apperrors.Wrap(deeper)

	fmt.Printf("%s\n\n", all)

	// Output:
	// second: first
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:48
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:47
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:51
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:50
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:48
	// /home/runner/work/go-api-boilerplate/go-api-boilerplate/pkg/errors/example_test.go:47
}
<|endoftext|>


<commit_before>package copy

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"github.com/containers/image/v5/docker/reference"
	"github.com/containers/image/v5/manifest"
	"github.com/containers/image/v5/types"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestOrderedSet(t *testing.T) {
	for _, c := range []struct{ input, expected []string }{
		{[]string{}, []string{}},
		{[]string{"a", "b", "c"}, []string{"a", "b", "c"}},
		{[]string{"a", "b", "a", "c"}, []string{"a", "b", "c"}},
	} {
		os := newOrderedSet()
		for _, s := range c.input {
			os.append(s)
		}
		assert.Equal(t, c.expected, os.list, fmt.Sprintf("%#v", c.input))
	}
}

// fakeImageSource is an implementation of types.Image which only returns itself as a MIME type in Manifest
// except that "" means “reading the manifest should fail”
type fakeImageSource string

func (f fakeImageSource) Reference() types.ImageReference {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Manifest(ctx context.Context) ([]byte, string, error) {
	if string(f) == "" {
		return nil, "", errors.New("Manifest() directed to fail")
	}
	return nil, string(f), nil
}
func (f fakeImageSource) Signatures(context.Context) ([][]byte, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) ConfigInfo() types.BlobInfo {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) ConfigBlob(context.Context) ([]byte, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) OCIConfig(context.Context) (*v1.Image, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) LayerInfos() []types.BlobInfo {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) EmbeddedDockerReferenceConflicts(ref reference.Named) bool {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Inspect(context.Context) (*types.ImageInspectInfo, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUpdateOptions) bool {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) UpdatedImage(ctx context.Context, options types.ManifestUpdateOptions) (types.Image, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) SupportsEncryption(ctx context.Context) bool {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Size() (int64, error) {
	panic("Unexpected call to a mock function")
}

func TestDetermineManifestConversion(t *testing.T) {
	supportS1S2OCI := []string{
		v1.MediaTypeImageManifest,
		manifest.DockerV2Schema2MediaType,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportS1OCI := []string{
		v1.MediaTypeImageManifest,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportS1S2 := []string{
		manifest.DockerV2Schema2MediaType,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportOnlyS1 := []string{
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}

	cases := []struct {
		description             string
		sourceType              string
		destTypes               []string
		expectedUpdate          string
		expectedOtherCandidates []string
	}{
		// Destination accepts anything — no conversion necessary
		{"s1→anything", manifest.DockerV2Schema1SignedMediaType, nil, "", []string{}},
		{"s2→anything", manifest.DockerV2Schema2MediaType, nil, "", []string{}},
		// Destination accepts the unmodified original
		{"s1→s1s2", manifest.DockerV2Schema1SignedMediaType, supportS1S2, "", []string{manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1MediaType}},
		{"s2→s1s2", manifest.DockerV2Schema2MediaType, supportS1S2, "", supportOnlyS1},
		{"s1→s1", manifest.DockerV2Schema1SignedMediaType, supportOnlyS1, "", []string{manifest.DockerV2Schema1MediaType}},
		// text/plain is normalized to s1, and if the destination accepts s1, no conversion happens.
		{"text→s1s2", "text/plain", supportS1S2, "", []string{manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1MediaType}},
		{"text→s1", "text/plain", supportOnlyS1, "", []string{manifest.DockerV2Schema1MediaType}},
		// Conversion necessary, a preferred format is acceptable
		{"s2→s1", manifest.DockerV2Schema2MediaType, supportOnlyS1, manifest.DockerV2Schema1SignedMediaType, []string{manifest.DockerV2Schema1MediaType}},
		// Conversion necessary, a preferred format is not acceptable
		{"s2→OCI", manifest.DockerV2Schema2MediaType, []string{v1.MediaTypeImageManifest}, v1.MediaTypeImageManifest, []string{}},
		// text/plain is converted if the destination does not accept s1
		{"text→s2", "text/plain", []string{manifest.DockerV2Schema2MediaType}, manifest.DockerV2Schema2MediaType, []string{}},
		// Conversion necessary, try the preferred formats in order.
		// We abuse manifest.DockerV2ListMediaType here as a MIME type which is not in supportS1S2OCI,
		// but is still recognized by manifest.NormalizedMIMEType and not normalized to s1
		{
			"special→s2", manifest.DockerV2ListMediaType, supportS1S2OCI, manifest.DockerV2Schema2MediaType,
			[]string{manifest.DockerV2Schema1SignedMediaType, v1.MediaTypeImageManifest, manifest.DockerV2Schema1MediaType},
		},
		{
			"special→s1", manifest.DockerV2ListMediaType, supportS1OCI, manifest.DockerV2Schema1SignedMediaType,
			[]string{v1.MediaTypeImageManifest, manifest.DockerV2Schema1MediaType},
		},
		{
			"special→OCI", manifest.DockerV2ListMediaType, []string{v1.MediaTypeImageManifest, "other options", "with lower priority"}, v1.MediaTypeImageManifest,
			[]string{"other options", "with lower priority"},
		},
	}

	for _, c := range cases {
		src := fakeImageSource(c.sourceType)
		ic := &imageCopier{
			manifestUpdates:            &types.ManifestUpdateOptions{},
			src:                        src,
			cannotModifyManifestReason: "",
		}
		preferredMIMEType, otherCandidates, err := ic.determineManifestConversion(context.Background(), c.destTypes, "", false)
		require.NoError(t, err, c.description)
		assert.Equal(t, c.expectedUpdate, ic.manifestUpdates.ManifestMIMEType, c.description)
		if c.expectedUpdate == "" {
			assert.Equal(t, manifest.NormalizedMIMEType(c.sourceType), preferredMIMEType, c.description)
		} else {
			assert.Equal(t, c.expectedUpdate, preferredMIMEType, c.description)
		}
		assert.Equal(t, c.expectedOtherCandidates, otherCandidates, c.description)
	}

	// Whatever the input is, with !canModifyManifest we return "keep the original as is"
	for _, c := range cases {
		src := fakeImageSource(c.sourceType)
		ic := &imageCopier{
			manifestUpdates:            &types.ManifestUpdateOptions{},
			src:                        src,
			cannotModifyManifestReason: "Preserving digests",
		}
		preferredMIMEType, otherCandidates, err := ic.determineManifestConversion(context.Background(), c.destTypes, "", false)
		require.NoError(t, err, c.description)
		assert.Equal(t, "", ic.manifestUpdates.ManifestMIMEType, c.description)
		assert.Equal(t, manifest.NormalizedMIMEType(c.sourceType), preferredMIMEType, c.description)
		assert.Equal(t, []string{}, otherCandidates, c.description)
	}

	// With forceManifestMIMEType, the output is always the forced manifest type (in this case oci manifest)
	for _, c := range cases {
		src := fakeImageSource(c.sourceType)
		ic := &imageCopier{
			manifestUpdates:            &types.ManifestUpdateOptions{},
			src:                        src,
			cannotModifyManifestReason: "",
		}
		preferredMIMEType, otherCandidates, err := ic.determineManifestConversion(context.Background(), c.destTypes, v1.MediaTypeImageManifest, false)
		require.NoError(t, err, c.description)
		assert.Equal(t, v1.MediaTypeImageManifest, ic.manifestUpdates.ManifestMIMEType, c.description)
		assert.Equal(t, v1.MediaTypeImageManifest, preferredMIMEType, c.description)
		assert.Equal(t, []string{}, otherCandidates, c.description)
	}

	// Error reading the manifest — smoke test only.
	ic := imageCopier{
		manifestUpdates:            &types.ManifestUpdateOptions{},
		src:                        fakeImageSource(""),
		cannotModifyManifestReason: "",
	}
	_, _, err := ic.determineManifestConversion(context.Background(), supportS1S2, "", false)
	assert.Error(t, err)
}

func TestIsMultiImage(t *testing.T) {
	// MIME type is available; more or less a smoke test, other cases are handled in manifest.MIMETypeIsMultiImage
	for _, c := range []struct {
		mt       string
		expected bool
	}{
		{manifest.DockerV2ListMediaType, true},
		{manifest.DockerV2Schema2MediaType, false},
		{v1.MediaTypeImageManifest, false},
		{v1.MediaTypeImageIndex, true},
	} {
		src := fakeImageSource(c.mt)
		res, err := isMultiImage(context.Background(), src)
		require.NoError(t, err)
		assert.Equal(t, c.expected, res, c.mt)
	}

	// Error getting manifest MIME type
	src := fakeImageSource("")
	_, err := isMultiImage(context.Background(), src)
	assert.Error(t, err)
}

func TestDetermineManifestListConversion(t *testing.T) {
	supportS1S2OCI := []string{
		v1.MediaTypeImageIndex,
		v1.MediaTypeImageManifest,
		manifest.DockerV2ListMediaType,
		manifest.DockerV2Schema2MediaType,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportS1S2 := []string{
		manifest.DockerV2ListMediaType,
		manifest.DockerV2Schema2MediaType,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportOnlyOCI := []string{
		v1.MediaTypeImageIndex,
		v1.MediaTypeImageManifest,
	}
	supportOnlyS1 := []string{
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}

	cases := []struct {
		description             string
		sourceType              string
		destTypes               []string
		expectedUpdate          string
		expectedOtherCandidates []string
	}{
		// Destination accepts anything — try all variants
		{"s2→anything", manifest.DockerV2ListMediaType, nil, "", []string{v1.MediaTypeImageIndex}},
		{"OCI→anything", v1.MediaTypeImageIndex, nil, "", []string{manifest.DockerV2ListMediaType}},
		// Destination accepts the unmodified original
		{"s2→s1s2OCI", manifest.DockerV2ListMediaType, supportS1S2OCI, "", []string{v1.MediaTypeImageIndex}},
		{"OCI→s1s2OCI", v1.MediaTypeImageIndex, supportS1S2OCI, "", []string{manifest.DockerV2ListMediaType}},
		{"s2→s1s2", manifest.DockerV2ListMediaType, supportS1S2, "", []string{}},
		{"OCI→OCI", v1.MediaTypeImageIndex, supportOnlyOCI, "", []string{}},
		// Conversion necessary, try the preferred formats in order.
		{"special→OCI", "unrecognized", supportS1S2OCI, v1.MediaTypeImageIndex, []string{manifest.DockerV2ListMediaType}},
		{"special→s2", "unrecognized", supportS1S2, manifest.DockerV2ListMediaType, []string{}},
	}

	for _, c := range cases {
		copier := &copier{}
		preferredMIMEType, otherCandidates, err := copier.determineListConversion(c.sourceType, c.destTypes, "")
		require.NoError(t, err, c.description)
		if c.expectedUpdate == "" {
			assert.Equal(t, manifest.NormalizedMIMEType(c.sourceType), preferredMIMEType, c.description)
		} else {
			assert.Equal(t, c.expectedUpdate, preferredMIMEType, c.description)
		}
		assert.Equal(t, c.expectedOtherCandidates, otherCandidates, c.description)
	}

	// With forceManifestMIMEType, the output is always the forced manifest type (in this case OCI index)
	for _, c := range cases {
		copier := &copier{}
		preferredMIMEType, otherCandidates, err := copier.determineListConversion(c.sourceType, c.destTypes, v1.MediaTypeImageIndex)
		require.NoError(t, err, c.description)
		assert.Equal(t, v1.MediaTypeImageIndex, preferredMIMEType, c.description)
		assert.Equal(t, []string{}, otherCandidates, c.description)
	}

	// The destination doesn’t support list formats at all
	copier := &copier{}
	_, _, err := copier.determineListConversion(v1.MediaTypeImageIndex, supportOnlyS1, "")
	assert.Error(t, err)
}
<commit_msg>Use a separate fakeUnparsedImage for TestIsMultiImage<commit_after>package copy

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"github.com/containers/image/v5/docker/reference"
	"github.com/containers/image/v5/internal/testing/mocks"
	"github.com/containers/image/v5/manifest"
	"github.com/containers/image/v5/types"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestOrderedSet(t *testing.T) {
	for _, c := range []struct{ input, expected []string }{
		{[]string{}, []string{}},
		{[]string{"a", "b", "c"}, []string{"a", "b", "c"}},
		{[]string{"a", "b", "a", "c"}, []string{"a", "b", "c"}},
	} {
		os := newOrderedSet()
		for _, s := range c.input {
			os.append(s)
		}
		assert.Equal(t, c.expected, os.list, fmt.Sprintf("%#v", c.input))
	}
}

// fakeImageSource is an implementation of types.Image which only returns itself as a MIME type in Manifest
// except that "" means “reading the manifest should fail”
type fakeImageSource string

func (f fakeImageSource) Reference() types.ImageReference {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Manifest(ctx context.Context) ([]byte, string, error) {
	if string(f) == "" {
		return nil, "", errors.New("Manifest() directed to fail")
	}
	return nil, string(f), nil
}
func (f fakeImageSource) Signatures(context.Context) ([][]byte, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) ConfigInfo() types.BlobInfo {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) ConfigBlob(context.Context) ([]byte, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) OCIConfig(context.Context) (*v1.Image, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) LayerInfos() []types.BlobInfo {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) EmbeddedDockerReferenceConflicts(ref reference.Named) bool {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Inspect(context.Context) (*types.ImageInspectInfo, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUpdateOptions) bool {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) UpdatedImage(ctx context.Context, options types.ManifestUpdateOptions) (types.Image, error) {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) SupportsEncryption(ctx context.Context) bool {
	panic("Unexpected call to a mock function")
}
func (f fakeImageSource) Size() (int64, error) {
	panic("Unexpected call to a mock function")
}

func TestDetermineManifestConversion(t *testing.T) {
	supportS1S2OCI := []string{
		v1.MediaTypeImageManifest,
		manifest.DockerV2Schema2MediaType,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportS1OCI := []string{
		v1.MediaTypeImageManifest,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportS1S2 := []string{
		manifest.DockerV2Schema2MediaType,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportOnlyS1 := []string{
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}

	cases := []struct {
		description             string
		sourceType              string
		destTypes               []string
		expectedUpdate          string
		expectedOtherCandidates []string
	}{
		// Destination accepts anything — no conversion necessary
		{"s1→anything", manifest.DockerV2Schema1SignedMediaType, nil, "", []string{}},
		{"s2→anything", manifest.DockerV2Schema2MediaType, nil, "", []string{}},
		// Destination accepts the unmodified original
		{"s1→s1s2", manifest.DockerV2Schema1SignedMediaType, supportS1S2, "", []string{manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1MediaType}},
		{"s2→s1s2", manifest.DockerV2Schema2MediaType, supportS1S2, "", supportOnlyS1},
		{"s1→s1", manifest.DockerV2Schema1SignedMediaType, supportOnlyS1, "", []string{manifest.DockerV2Schema1MediaType}},
		// text/plain is normalized to s1, and if the destination accepts s1, no conversion happens.
		{"text→s1s2", "text/plain", supportS1S2, "", []string{manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1MediaType}},
		{"text→s1", "text/plain", supportOnlyS1, "", []string{manifest.DockerV2Schema1MediaType}},
		// Conversion necessary, a preferred format is acceptable
		{"s2→s1", manifest.DockerV2Schema2MediaType, supportOnlyS1, manifest.DockerV2Schema1SignedMediaType, []string{manifest.DockerV2Schema1MediaType}},
		// Conversion necessary, a preferred format is not acceptable
		{"s2→OCI", manifest.DockerV2Schema2MediaType, []string{v1.MediaTypeImageManifest}, v1.MediaTypeImageManifest, []string{}},
		// text/plain is converted if the destination does not accept s1
		{"text→s2", "text/plain", []string{manifest.DockerV2Schema2MediaType}, manifest.DockerV2Schema2MediaType, []string{}},
		// Conversion necessary, try the preferred formats in order.
		// We abuse manifest.DockerV2ListMediaType here as a MIME type which is not in supportS1S2OCI,
		// but is still recognized by manifest.NormalizedMIMEType and not normalized to s1
		{
			"special→s2", manifest.DockerV2ListMediaType, supportS1S2OCI, manifest.DockerV2Schema2MediaType,
			[]string{manifest.DockerV2Schema1SignedMediaType, v1.MediaTypeImageManifest, manifest.DockerV2Schema1MediaType},
		},
		{
			"special→s1", manifest.DockerV2ListMediaType, supportS1OCI, manifest.DockerV2Schema1SignedMediaType,
			[]string{v1.MediaTypeImageManifest, manifest.DockerV2Schema1MediaType},
		},
		{
			"special→OCI", manifest.DockerV2ListMediaType, []string{v1.MediaTypeImageManifest, "other options", "with lower priority"}, v1.MediaTypeImageManifest,
			[]string{"other options", "with lower priority"},
		},
	}

	for _, c := range cases {
		src := fakeImageSource(c.sourceType)
		ic := &imageCopier{
			manifestUpdates:            &types.ManifestUpdateOptions{},
			src:                        src,
			cannotModifyManifestReason: "",
		}
		preferredMIMEType, otherCandidates, err := ic.determineManifestConversion(context.Background(), c.destTypes, "", false)
		require.NoError(t, err, c.description)
		assert.Equal(t, c.expectedUpdate, ic.manifestUpdates.ManifestMIMEType, c.description)
		if c.expectedUpdate == "" {
			assert.Equal(t, manifest.NormalizedMIMEType(c.sourceType), preferredMIMEType, c.description)
		} else {
			assert.Equal(t, c.expectedUpdate, preferredMIMEType, c.description)
		}
		assert.Equal(t, c.expectedOtherCandidates, otherCandidates, c.description)
	}

	// Whatever the input is, with !canModifyManifest we return "keep the original as is"
	for _, c := range cases {
		src := fakeImageSource(c.sourceType)
		ic := &imageCopier{
			manifestUpdates:            &types.ManifestUpdateOptions{},
			src:                        src,
			cannotModifyManifestReason: "Preserving digests",
		}
		preferredMIMEType, otherCandidates, err := ic.determineManifestConversion(context.Background(), c.destTypes, "", false)
		require.NoError(t, err, c.description)
		assert.Equal(t, "", ic.manifestUpdates.ManifestMIMEType, c.description)
		assert.Equal(t, manifest.NormalizedMIMEType(c.sourceType), preferredMIMEType, c.description)
		assert.Equal(t, []string{}, otherCandidates, c.description)
	}

	// With forceManifestMIMEType, the output is always the forced manifest type (in this case oci manifest)
	for _, c := range cases {
		src := fakeImageSource(c.sourceType)
		ic := &imageCopier{
			manifestUpdates:            &types.ManifestUpdateOptions{},
			src:                        src,
			cannotModifyManifestReason: "",
		}
		preferredMIMEType, otherCandidates, err := ic.determineManifestConversion(context.Background(), c.destTypes, v1.MediaTypeImageManifest, false)
		require.NoError(t, err, c.description)
		assert.Equal(t, v1.MediaTypeImageManifest, ic.manifestUpdates.ManifestMIMEType, c.description)
		assert.Equal(t, v1.MediaTypeImageManifest, preferredMIMEType, c.description)
		assert.Equal(t, []string{}, otherCandidates, c.description)
	}

	// Error reading the manifest — smoke test only.
	ic := imageCopier{
		manifestUpdates:            &types.ManifestUpdateOptions{},
		src:                        fakeImageSource(""),
		cannotModifyManifestReason: "",
	}
	_, _, err := ic.determineManifestConversion(context.Background(), supportS1S2, "", false)
	assert.Error(t, err)
}

// fakeUnparsedImage is an implementation of types.UnparsedImage which only returns itself as a MIME type in Manifest,
// except that "" means “reading the manifest should fail”
type fakeUnparsedImage struct {
	mocks.ForbiddenUnparsedImage
	mt string
}

func (f fakeUnparsedImage) Manifest(ctx context.Context) ([]byte, string, error) {
	if f.mt == "" {
		return nil, "", errors.New("Manifest() directed to fail")
	}
	return nil, f.mt, nil
}

func TestIsMultiImage(t *testing.T) {
	// MIME type is available; more or less a smoke test, other cases are handled in manifest.MIMETypeIsMultiImage
	for _, c := range []struct {
		mt       string
		expected bool
	}{
		{manifest.DockerV2ListMediaType, true},
		{manifest.DockerV2Schema2MediaType, false},
		{v1.MediaTypeImageManifest, false},
		{v1.MediaTypeImageIndex, true},
	} {
		src := fakeUnparsedImage{mocks.ForbiddenUnparsedImage{}, c.mt}
		res, err := isMultiImage(context.Background(), src)
		require.NoError(t, err)
		assert.Equal(t, c.expected, res, c.mt)
	}

	// Error getting manifest MIME type
	src := fakeUnparsedImage{mocks.ForbiddenUnparsedImage{}, ""}
	_, err := isMultiImage(context.Background(), src)
	assert.Error(t, err)
}

func TestDetermineManifestListConversion(t *testing.T) {
	supportS1S2OCI := []string{
		v1.MediaTypeImageIndex,
		v1.MediaTypeImageManifest,
		manifest.DockerV2ListMediaType,
		manifest.DockerV2Schema2MediaType,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportS1S2 := []string{
		manifest.DockerV2ListMediaType,
		manifest.DockerV2Schema2MediaType,
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}
	supportOnlyOCI := []string{
		v1.MediaTypeImageIndex,
		v1.MediaTypeImageManifest,
	}
	supportOnlyS1 := []string{
		manifest.DockerV2Schema1SignedMediaType,
		manifest.DockerV2Schema1MediaType,
	}

	cases := []struct {
		description             string
		sourceType              string
		destTypes               []string
		expectedUpdate          string
		expectedOtherCandidates []string
	}{
		// Destination accepts anything — try all variants
		{"s2→anything", manifest.DockerV2ListMediaType, nil, "", []string{v1.MediaTypeImageIndex}},
		{"OCI→anything", v1.MediaTypeImageIndex, nil, "", []string{manifest.DockerV2ListMediaType}},
		// Destination accepts the unmodified original
		{"s2→s1s2OCI", manifest.DockerV2ListMediaType, supportS1S2OCI, "", []string{v1.MediaTypeImageIndex}},
		{"OCI→s1s2OCI", v1.MediaTypeImageIndex, supportS1S2OCI, "", []string{manifest.DockerV2ListMediaType}},
		{"s2→s1s2", manifest.DockerV2ListMediaType, supportS1S2, "", []string{}},
		{"OCI→OCI", v1.MediaTypeImageIndex, supportOnlyOCI, "", []string{}},
		// Conversion necessary, try the preferred formats in order.
		{"special→OCI", "unrecognized", supportS1S2OCI, v1.MediaTypeImageIndex, []string{manifest.DockerV2ListMediaType}},
		{"special→s2", "unrecognized", supportS1S2, manifest.DockerV2ListMediaType, []string{}},
	}

	for _, c := range cases {
		copier := &copier{}
		preferredMIMEType, otherCandidates, err := copier.determineListConversion(c.sourceType, c.destTypes, "")
		require.NoError(t, err, c.description)
		if c.expectedUpdate == "" {
			assert.Equal(t, manifest.NormalizedMIMEType(c.sourceType), preferredMIMEType, c.description)
		} else {
			assert.Equal(t, c.expectedUpdate, preferredMIMEType, c.description)
		}
		assert.Equal(t, c.expectedOtherCandidates, otherCandidates, c.description)
	}

	// With forceManifestMIMEType, the output is always the forced manifest type (in this case OCI index)
	for _, c := range cases {
		copier := &copier{}
		preferredMIMEType, otherCandidates, err := copier.determineListConversion(c.sourceType, c.destTypes, v1.MediaTypeImageIndex)
		require.NoError(t, err, c.description)
		assert.Equal(t, v1.MediaTypeImageIndex, preferredMIMEType, c.description)
		assert.Equal(t, []string{}, otherCandidates, c.description)
	}

	// The destination doesn’t support list formats at all
	copier := &copier{}
	_, _, err := copier.determineListConversion(v1.MediaTypeImageIndex, supportOnlyS1, "")
	assert.Error(t, err)
}
<|endoftext|>