<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package framework_test

import (
	"fmt"
	"math/rand"
	"sync"
	"testing"
	"time"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/cache"
	"k8s.io/kubernetes/pkg/controller/framework"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/sets"

	"github.com/google/gofuzz"
)

func Example() {
	// source simulates an apiserver object endpoint.
	source := framework.NewFakeControllerSource()

	// This will hold the downstream state, as we know it.
	downstream := cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc)

	// This will hold incoming changes. Note how we pass downstream in as a
	// KeyLister, that way resync operations will result in the correct set
	// of update/delete deltas.
	fifo := cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, nil, downstream)

	// Let's do threadsafe output to get predictable test results.
	deletionCounter := make(chan string, 1000)

	cfg := &framework.Config{
		Queue:            fifo,
		ListerWatcher:    source,
		ObjectType:       &api.Pod{},
		FullResyncPeriod: time.Millisecond * 100,
		RetryOnError:     false,

		// Let's implement a simple controller that just deletes
		// everything that comes in.
		Process: func(obj interface{}) error {
			// Obj is from the Pop method of the Queue we make above.
			newest := obj.(cache.Deltas).Newest()

			if newest.Type != cache.Deleted {
				// Update our downstream store.
				err := downstream.Add(newest.Object)
				if err != nil {
					return err
				}

				// Delete this object.
				source.Delete(newest.Object.(runtime.Object))
			} else {
				// Update our downstream store.
				err := downstream.Delete(newest.Object)
				if err != nil {
					return err
				}

				// fifo's KeyOf is easiest, because it handles
				// DeletedFinalStateUnknown markers.
				key, err := fifo.KeyOf(newest.Object)
				if err != nil {
					return err
				}

				// Report this deletion.
				deletionCounter <- key
			}
			return nil
		},
	}

	// Create the controller and run it until we close stop.
	stop := make(chan struct{})
	defer close(stop)
	go framework.New(cfg).Run(stop)

	// Let's add a few objects to the source.
	testIDs := []string{"a-hello", "b-controller", "c-framework"}
	for _, name := range testIDs {
		// Note that these pods are not valid-- the fake source doesn't
		// call validation or anything.
		source.Add(&api.Pod{ObjectMeta: api.ObjectMeta{Name: name}})
	}

	// Let's wait for the controller to process the things we just added.
	outputSet := sets.String{}
	for i := 0; i < len(testIDs); i++ {
		outputSet.Insert(<-deletionCounter)
	}

	for _, key := range outputSet.List() {
		fmt.Println(key)
	}
	// Output:
	// a-hello
	// b-controller
	// c-framework
}

func ExampleInformer() {
	// source simulates an apiserver object endpoint.
	source := framework.NewFakeControllerSource()

	// Let's do threadsafe output to get predictable test results.
	deletionCounter := make(chan string, 1000)

	// Make a controller that immediately deletes anything added to it, and
	// logs anything deleted.
	_, controller := framework.NewInformer(
		source,
		&api.Pod{},
		time.Millisecond*100,
		framework.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				source.Delete(obj.(runtime.Object))
			},
			DeleteFunc: func(obj interface{}) {
				key, err := framework.DeletionHandlingMetaNamespaceKeyFunc(obj)
				if err != nil {
					key = "oops something went wrong with the key"
				}

				// Report this deletion.
				deletionCounter <- key
			},
		},
	)

	// Run the controller and run it until we close stop.
	stop := make(chan struct{})
	defer close(stop)
	go controller.Run(stop)

	// Let's add a few objects to the source.
	testIDs := []string{"a-hello", "b-controller", "c-framework"}
	for _, name := range testIDs {
		// Note that these pods are not valid-- the fake source doesn't
		// call validation or anything.
		source.Add(&api.Pod{ObjectMeta: api.ObjectMeta{Name: name}})
	}

	// Let's wait for the controller to process the things we just added.
	outputSet := sets.String{}
	for i := 0; i < len(testIDs); i++ {
		outputSet.Insert(<-deletionCounter)
	}

	for _, key := range outputSet.List() {
		fmt.Println(key)
	}
	// Output:
	// a-hello
	// b-controller
	// c-framework
}

func TestHammerController(t *testing.T) {
	// This test executes a bunch of requests through the fake source and
	// controller framework to make sure there's no locking/threading
	// errors. If an error happens, it should hang forever or trigger the
	// race detector.

	// source simulates an apiserver object endpoint.
	source := framework.NewFakeControllerSource()

	// Let's do threadsafe output to get predictable test results.
	outputSetLock := sync.Mutex{}
	// map of key to operations done on the key
	outputSet := map[string][]string{}

	recordFunc := func(eventType string, obj interface{}) {
		key, err := framework.DeletionHandlingMetaNamespaceKeyFunc(obj)
		if err != nil {
			t.Errorf("something wrong with key: %v", err)
			key = "oops something went wrong with the key"
		}

		// Record some output when items are deleted.
		outputSetLock.Lock()
		defer outputSetLock.Unlock()
		outputSet[key] = append(outputSet[key], eventType)
	}

	// Make a controller which just logs all the changes it gets.
	_, controller := framework.NewInformer(
		source,
		&api.Pod{},
		time.Millisecond*100,
		framework.ResourceEventHandlerFuncs{
			AddFunc:    func(obj interface{}) { recordFunc("add", obj) },
			UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) },
			DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) },
		},
	)

	if controller.HasSynced() {
		t.Errorf("Expected HasSynced() to return false before we started the controller")
	}

	// Run the controller and run it until we close stop.
	stop := make(chan struct{})
	go controller.Run(stop)

	// Let's wait for the controller to do its initial sync
	time.Sleep(100 * time.Millisecond)
	if !controller.HasSynced() {
		t.Errorf("Expected HasSynced() to return true after the initial sync")
	}

	wg := sync.WaitGroup{}
	const threads = 3
	wg.Add(threads)
	for i := 0; i < threads; i++ {
		go func() {
			defer wg.Done()
			// Let's add a few objects to the source.
			currentNames := sets.String{}
			rs := rand.NewSource(rand.Int63())
			f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs)
			r := rand.New(rs) // Mustn't use r and f concurrently!
			for i := 0; i < 100; i++ {
				var name string
				var isNew bool
				if currentNames.Len() == 0 || r.Intn(3) == 1 {
					f.Fuzz(&name)
					isNew = true
				} else {
					l := currentNames.List()
					name = l[r.Intn(len(l))]
				}

				pod := &api.Pod{}
				f.Fuzz(pod)
				pod.ObjectMeta.Name = name
				pod.ObjectMeta.Namespace = "default"
				// Add, update, or delete randomly.
				// Note that these pods are not valid-- the fake source doesn't
				// call validation or perform any other checking.
				if isNew {
					currentNames.Insert(name)
					source.Add(pod)
					continue
				}
				switch r.Intn(2) {
				case 0:
					currentNames.Insert(name)
					source.Modify(pod)
				case 1:
					currentNames.Delete(name)
					source.Delete(pod)
				}
			}
		}()
	}
	wg.Wait()

	// Let's wait for the controller to finish processing the things we just added.
	time.Sleep(100 * time.Millisecond)
	close(stop)

	outputSetLock.Lock()
	t.Logf("got: %#v", outputSet)
}

func TestUpdate(t *testing.T) {
	// This test is going to exercise the various paths that result in a
	// call to update.

	// source simulates an apiserver object endpoint.
	source := framework.NewFakeControllerSource()

	const (
		FROM       = "from"
		ADD_MISSED = "missed the add event"
		TO         = "to"
	)

	// These are the transitions we expect to see; because this is
	// asynchronous, there are a lot of valid possibilities.
	type pair struct{ from, to string }
	allowedTransitions := map[pair]bool{
		pair{FROM, TO}:         true,
		pair{FROM, ADD_MISSED}: true,
		pair{ADD_MISSED, TO}:   true,

		// Because a resync can happen when we've already observed one
		// of the above but before the item is deleted.
		pair{TO, TO}: true,
		// Because a resync could happen before we observe an update.
		pair{FROM, FROM}: true,
	}

	pod := func(name, check string) *api.Pod {
		return &api.Pod{
			ObjectMeta: api.ObjectMeta{
				Name:   name,
				Labels: map[string]string{"check": check},
			},
		}
	}

	tests := []func(string){
		func(name string) {
			name = "a-" + name
			source.Add(pod(name, FROM))
			source.Modify(pod(name, TO))
		},
		func(name string) {
			name = "b-" + name
			source.Add(pod(name, FROM))
			source.ModifyDropWatch(pod(name, TO))
		},
		func(name string) {
			name = "c-" + name
			source.AddDropWatch(pod(name, FROM))
			source.Modify(pod(name, ADD_MISSED))
			source.Modify(pod(name, TO))
		},
		func(name string) {
			name = "d-" + name
			source.Add(pod(name, FROM))
		},
	}

	const threads = 3

	var testDoneWG sync.WaitGroup
	testDoneWG.Add(threads * len(tests))

	// Make a controller that deletes things once it observes an update.
	// It calls Done() on the wait group on deletions so we can tell when
	// everything we've added has been deleted.
	_, controller := framework.NewInformer(
		source,
		&api.Pod{},
		time.Millisecond*1,
		framework.ResourceEventHandlerFuncs{
			UpdateFunc: func(oldObj, newObj interface{}) {
				o, n := oldObj.(*api.Pod), newObj.(*api.Pod)
				from, to := o.Labels["check"], n.Labels["check"]
				if !allowedTransitions[pair{from, to}] {
					t.Errorf("observed transition %q -> %q for %v", from, to, n.Name)
				}
				source.Delete(n)
			},
			DeleteFunc: func(obj interface{}) {
				testDoneWG.Done()
			},
		},
	)

	// Run the controller and run it until we close stop.
	// Once Run() is called, calls to testDoneWG.Done() might start, so
	// all testDoneWG.Add() calls must happen before this point
	stop := make(chan struct{})
	go controller.Run(stop)

	// run every test a few times, in parallel
	var wg sync.WaitGroup
	wg.Add(threads * len(tests))
	for i := 0; i < threads; i++ {
		for j, f := range tests {
			go func(name string, f func(string)) {
				defer wg.Done()
				f(name)
			}(fmt.Sprintf("%v-%v", i, j), f)
		}
	}
	wg.Wait()

	// Let's wait for the controller to process the things we just added.
	testDoneWG.Wait()
	close(stop)
}
<commit_msg>Only delete pods when they reach final state<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package framework_test

import (
	"fmt"
	"math/rand"
	"sync"
	"testing"
	"time"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/cache"
	"k8s.io/kubernetes/pkg/controller/framework"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/sets"

	"github.com/google/gofuzz"
)

func Example() {
	// source simulates an apiserver object endpoint.
	source := framework.NewFakeControllerSource()

	// This will hold the downstream state, as we know it.
	downstream := cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc)

	// This will hold incoming changes. Note how we pass downstream in as a
	// KeyLister, that way resync operations will result in the correct set
	// of update/delete deltas.
	fifo := cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, nil, downstream)

	// Let's do threadsafe output to get predictable test results.
	deletionCounter := make(chan string, 1000)

	cfg := &framework.Config{
		Queue:            fifo,
		ListerWatcher:    source,
		ObjectType:       &api.Pod{},
		FullResyncPeriod: time.Millisecond * 100,
		RetryOnError:     false,

		// Let's implement a simple controller that just deletes
		// everything that comes in.
		Process: func(obj interface{}) error {
			// Obj is from the Pop method of the Queue we make above.
			newest := obj.(cache.Deltas).Newest()

			if newest.Type != cache.Deleted {
				// Update our downstream store.
				err := downstream.Add(newest.Object)
				if err != nil {
					return err
				}

				// Delete this object.
				source.Delete(newest.Object.(runtime.Object))
			} else {
				// Update our downstream store.
				err := downstream.Delete(newest.Object)
				if err != nil {
					return err
				}

				// fifo's KeyOf is easiest, because it handles
				// DeletedFinalStateUnknown markers.
				key, err := fifo.KeyOf(newest.Object)
				if err != nil {
					return err
				}

				// Report this deletion.
				deletionCounter <- key
			}
			return nil
		},
	}

	// Create the controller and run it until we close stop.
	stop := make(chan struct{})
	defer close(stop)
	go framework.New(cfg).Run(stop)

	// Let's add a few objects to the source.
	testIDs := []string{"a-hello", "b-controller", "c-framework"}
	for _, name := range testIDs {
		// Note that these pods are not valid-- the fake source doesn't
		// call validation or anything.
		source.Add(&api.Pod{ObjectMeta: api.ObjectMeta{Name: name}})
	}

	// Let's wait for the controller to process the things we just added.
	outputSet := sets.String{}
	for i := 0; i < len(testIDs); i++ {
		outputSet.Insert(<-deletionCounter)
	}

	for _, key := range outputSet.List() {
		fmt.Println(key)
	}
	// Output:
	// a-hello
	// b-controller
	// c-framework
}

func ExampleInformer() {
	// source simulates an apiserver object endpoint.
	source := framework.NewFakeControllerSource()

	// Let's do threadsafe output to get predictable test results.
	deletionCounter := make(chan string, 1000)

	// Make a controller that immediately deletes anything added to it, and
	// logs anything deleted.
	_, controller := framework.NewInformer(
		source,
		&api.Pod{},
		time.Millisecond*100,
		framework.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				source.Delete(obj.(runtime.Object))
			},
			DeleteFunc: func(obj interface{}) {
				key, err := framework.DeletionHandlingMetaNamespaceKeyFunc(obj)
				if err != nil {
					key = "oops something went wrong with the key"
				}

				// Report this deletion.
				deletionCounter <- key
			},
		},
	)

	// Run the controller and run it until we close stop.
	stop := make(chan struct{})
	defer close(stop)
	go controller.Run(stop)

	// Let's add a few objects to the source.
	testIDs := []string{"a-hello", "b-controller", "c-framework"}
	for _, name := range testIDs {
		// Note that these pods are not valid-- the fake source doesn't
		// call validation or anything.
		source.Add(&api.Pod{ObjectMeta: api.ObjectMeta{Name: name}})
	}

	// Let's wait for the controller to process the things we just added.
	outputSet := sets.String{}
	for i := 0; i < len(testIDs); i++ {
		outputSet.Insert(<-deletionCounter)
	}

	for _, key := range outputSet.List() {
		fmt.Println(key)
	}
	// Output:
	// a-hello
	// b-controller
	// c-framework
}

func TestHammerController(t *testing.T) {
	// This test executes a bunch of requests through the fake source and
	// controller framework to make sure there's no locking/threading
	// errors. If an error happens, it should hang forever or trigger the
	// race detector.

	// source simulates an apiserver object endpoint.
	source := framework.NewFakeControllerSource()

	// Let's do threadsafe output to get predictable test results.
	outputSetLock := sync.Mutex{}
	// map of key to operations done on the key
	outputSet := map[string][]string{}

	recordFunc := func(eventType string, obj interface{}) {
		key, err := framework.DeletionHandlingMetaNamespaceKeyFunc(obj)
		if err != nil {
			t.Errorf("something wrong with key: %v", err)
			key = "oops something went wrong with the key"
		}

		// Record some output when items are deleted.
		outputSetLock.Lock()
		defer outputSetLock.Unlock()
		outputSet[key] = append(outputSet[key], eventType)
	}

	// Make a controller which just logs all the changes it gets.
	_, controller := framework.NewInformer(
		source,
		&api.Pod{},
		time.Millisecond*100,
		framework.ResourceEventHandlerFuncs{
			AddFunc:    func(obj interface{}) { recordFunc("add", obj) },
			UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) },
			DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) },
		},
	)

	if controller.HasSynced() {
		t.Errorf("Expected HasSynced() to return false before we started the controller")
	}

	// Run the controller and run it until we close stop.
	stop := make(chan struct{})
	go controller.Run(stop)

	// Let's wait for the controller to do its initial sync
	time.Sleep(100 * time.Millisecond)
	if !controller.HasSynced() {
		t.Errorf("Expected HasSynced() to return true after the initial sync")
	}

	wg := sync.WaitGroup{}
	const threads = 3
	wg.Add(threads)
	for i := 0; i < threads; i++ {
		go func() {
			defer wg.Done()
			// Let's add a few objects to the source.
			currentNames := sets.String{}
			rs := rand.NewSource(rand.Int63())
			f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs)
			r := rand.New(rs) // Mustn't use r and f concurrently!
			for i := 0; i < 100; i++ {
				var name string
				var isNew bool
				if currentNames.Len() == 0 || r.Intn(3) == 1 {
					f.Fuzz(&name)
					isNew = true
				} else {
					l := currentNames.List()
					name = l[r.Intn(len(l))]
				}

				pod := &api.Pod{}
				f.Fuzz(pod)
				pod.ObjectMeta.Name = name
				pod.ObjectMeta.Namespace = "default"
				// Add, update, or delete randomly.
				// Note that these pods are not valid-- the fake source doesn't
				// call validation or perform any other checking.
				if isNew {
					currentNames.Insert(name)
					source.Add(pod)
					continue
				}
				switch r.Intn(2) {
				case 0:
					currentNames.Insert(name)
					source.Modify(pod)
				case 1:
					currentNames.Delete(name)
					source.Delete(pod)
				}
			}
		}()
	}
	wg.Wait()

	// Let's wait for the controller to finish processing the things we just added.
	time.Sleep(100 * time.Millisecond)
	close(stop)

	outputSetLock.Lock()
	t.Logf("got: %#v", outputSet)
}

func TestUpdate(t *testing.T) {
	// This test is going to exercise the various paths that result in a
	// call to update.

	// source simulates an apiserver object endpoint.
	source := framework.NewFakeControllerSource()

	const (
		FROM       = "from"
		ADD_MISSED = "missed the add event"
		TO         = "to"
	)

	// These are the transitions we expect to see; because this is
	// asynchronous, there are a lot of valid possibilities.
	type pair struct{ from, to string }
	allowedTransitions := map[pair]bool{
		pair{FROM, TO}:         true,
		pair{FROM, ADD_MISSED}: true,
		pair{ADD_MISSED, TO}:   true,

		// Because a resync can happen when we've already observed one
		// of the above but before the item is deleted.
		pair{TO, TO}: true,
		// Because a resync could happen before we observe an update.
		pair{FROM, FROM}: true,
	}

	pod := func(name, check string, final bool) *api.Pod {
		p := &api.Pod{
			ObjectMeta: api.ObjectMeta{
				Name:   name,
				Labels: map[string]string{"check": check},
			},
		}
		if final {
			p.Labels["final"] = "true"
		}
		return p
	}
	deletePod := func(p *api.Pod) bool {
		return p.Labels["final"] == "true"
	}

	tests := []func(string){
		func(name string) {
			name = "a-" + name
			source.Add(pod(name, FROM, false))
			source.Modify(pod(name, TO, true))
		},
		func(name string) {
			name = "b-" + name
			source.Add(pod(name, FROM, false))
			source.ModifyDropWatch(pod(name, TO, true))
		},
		func(name string) {
			name = "c-" + name
			source.AddDropWatch(pod(name, FROM, false))
			source.Modify(pod(name, ADD_MISSED, false))
			source.Modify(pod(name, TO, true))
		},
		func(name string) {
			name = "d-" + name
			source.Add(pod(name, FROM, true))
		},
	}

	const threads = 3

	var testDoneWG sync.WaitGroup
	testDoneWG.Add(threads * len(tests))

	// Make a controller that deletes things once it observes an update.
	// It calls Done() on the wait group on deletions so we can tell when
	// everything we've added has been deleted.
	_, controller := framework.NewInformer(
		source,
		&api.Pod{},
		time.Millisecond*1,
		framework.ResourceEventHandlerFuncs{
			UpdateFunc: func(oldObj, newObj interface{}) {
				o, n := oldObj.(*api.Pod), newObj.(*api.Pod)
				from, to := o.Labels["check"], n.Labels["check"]
				if !allowedTransitions[pair{from, to}] {
					t.Errorf("observed transition %q -> %q for %v", from, to, n.Name)
				}
				if deletePod(n) {
					source.Delete(n)
				}
			},
			DeleteFunc: func(obj interface{}) {
				testDoneWG.Done()
			},
		},
	)

	// Run the controller and run it until we close stop.
	// Once Run() is called, calls to testDoneWG.Done() might start, so
	// all testDoneWG.Add() calls must happen before this point
	stop := make(chan struct{})
	go controller.Run(stop)

	// run every test a few times, in parallel
	var wg sync.WaitGroup
	wg.Add(threads * len(tests))
	for i := 0; i < threads; i++ {
		for j, f := range tests {
			go func(name string, f func(string)) {
				defer wg.Done()
				f(name)
			}(fmt.Sprintf("%v-%v", i, j), f)
		}
	}
	wg.Wait()

	// Let's wait for the controller to process the things we just added.
	testDoneWG.Wait()
	close(stop)
}
<|endoftext|>


<commit_before>// Copyright (c) 2019 The OpenSDS Authors All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
This module implements a entry into the OpenSDS metrics controller service.

*/

package metrics

import (
	"encoding/json"
	log "github.com/golang/glog"
	"github.com/opensds/opensds/pkg/dock/client"
	"github.com/opensds/opensds/pkg/model"
	pb "github.com/opensds/opensds/pkg/model/proto"
	"io/ioutil"
	"net/http"
	"strconv"
)

// Controller is an interface for exposing some operations of metric controllers.
type Controller interface {
	GetLatestMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error)
	GetInstantMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error)
	GetRangeMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error)
	SetDock(dockInfo *model.DockSpec)
}

// NewController method creates a controller structure and expose its pointer.
func NewController() Controller {
	return &controller{
		Client: client.NewClient(),
	}
}

type controller struct {
	client.Client
	DockInfo *model.DockSpec
}

// latest+instant metrics structs begin
type InstantMetricReponseFromPrometheus struct {
	Status string `json:"status"`
	Data   Data   `json:"data"`
}
type Metric struct {
	Name       string `json:"__name__"`
	Device     string `json:"device"`
	InstanceID string `json:"instanceID"`
	Job        string `json:"job"`
}
type Result struct {
	Metric Metric        `json:"metric"`
	Value  []interface{} `json:"value"`
}
type Data struct {
	ResultType string   `json:"resultType"`
	Result     []Result `json:"result"`
}

// latest+instant metrics structs end

// latest+range metrics structs begin
type RangeMetricReponseFromPrometheus struct {
	Status string    `json:"status"`
	Data   RangeData `json:"data"`
}
type RangeMetric struct {
	Name     string `json:"__name__"`
	Device   string `json:"device"`
	Instance string `json:"instance"`
	Job      string `json:"job"`
}
type RangeResult struct {
	Metric RangeMetric     `json:"metric"`
	Values [][]interface{} `json:"values"`
}
type RangeData struct {
	ResultType string        `json:"resultType"`
	Result     []RangeResult `json:"result"`
}

// latest+range metrics structs end

func (c *controller) GetLatestMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error) {

	// make a call to Prometheus, convert the response to our format, return
	response, err := http.Get("http://localhost:9090/api/v1/query?query=" + opt.MetricName)
	if err != nil {
		log.Infof("The HTTP query request failed with error %s\n", err)
	} else {
		data, _ := ioutil.ReadAll(response.Body)
		log.Info(string(data))
		// unmarshal the JSON response into a struct (generated using the JSON, using this https://mholt.github.io/json-to-go/
		var fv InstantMetricReponseFromPrometheus
		err0 := json.Unmarshal(data, &fv)
		log.Error(err0)
		metrics := make([]model.MetricSpec, len(fv.Data.Result))

		// now convert to our repsonse struct, so we can marshal it and send out the JSON
		for i, res := range fv.Data.Result {
			metrics[i].InstanceID = res.Metric.InstanceID
			metrics[i].Name = res.Metric.Name
			metrics[i].InstanceName = res.Metric.Device
			metricValues := make([]*model.Metric, 0)
			metricValue := &model.Metric{}
			for _, v := range res.Value {

				switch v.(type) {
				case string:
					metricValue.Value, err = strconv.ParseFloat(v.(string), 64)

					//metricValues = append(metricValues, metricValue)
					//metrics[i].MetricValues[j].Value, _ = strconv.ParseFloat(v.(string), 64)
				case float64:
					secs := int64(v.(float64))
					metricValue.Timestamp = secs
					//metrics[i].MetricValues[j].Timestamp = secs
				default:
					log.Info(v, "is of a type I don't know how to handle")
				}

			}
			metricValues = append(metricValues, metricValue)
			metrics[i].MetricValues = metricValues
		}

		bArr, _ := json.Marshal(metrics)
		log.Infof("metrics response json is %s", string(bArr))

		if err != nil {
			log.Error(err)
		}
		return &metrics, err

	}
	return nil, err
}

func (c *controller) GetInstantMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error) {

	// make a call to Prometheus, convert the response to our format, return
	response, err := http.Get("http://localhost:9090/api/v1/query?query=" + opt.MetricName + "&time=" + opt.StartTime)
	if err != nil {
		log.Infof("The HTTP query request failed with error %s\n", err)
	} else {
		data, _ := ioutil.ReadAll(response.Body)
		log.Infof("response data is %s", string(data))
		// unmarshal the JSON response into a struct (generated using the JSON, using this https://mholt.github.io/json-to-go/
		var fv InstantMetricReponseFromPrometheus
		err0 := json.Unmarshal(data, &fv)
		log.Error(err0)
		metrics := make([]model.MetricSpec, len(fv.Data.Result))

		// now convert to our repsonse struct, so we can marshal it and send out the JSON
		for i, res := range fv.Data.Result {
			metrics[i].InstanceID = res.Metric.InstanceID
			metrics[i].Name = res.Metric.Name
			metrics[i].InstanceName = res.Metric.Device
			metricValues := make([]*model.Metric, 0)
			metricValue := &model.Metric{}
			for _, v := range res.Value {

				switch v.(type) {
				case string:
					metricValue.Value, err = strconv.ParseFloat(v.(string), 64)

					//metricValues = append(metricValues, metricValue)
					//metrics[i].MetricValues[j].Value, _ = strconv.ParseFloat(v.(string), 64)
				case float64:
					secs := int64(v.(float64))
					metricValue.Timestamp = secs
					//metrics[i].MetricValues[j].Timestamp = secs
				default:
					log.Info(v, "is of a type I don't know how to handle")
				}

			}
			metricValues = append(metricValues, metricValue)
			metrics[i].MetricValues = metricValues
		}

		bArr, _ := json.Marshal(metrics)
		log.Infof("metrics response json is %s", string(bArr))

		if err != nil {
			log.Error(err)
		}
		return &metrics, err

	}
	return nil, err
}

func (c *controller) GetRangeMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error) {

	//var metrics []model.MetricSpec
	// make a call to Prometheus, convert the response to our format, return
	response, err := http.Get("http://localhost:9090/api/v1/query_range?query=" + opt.MetricName + "&start=" + opt.StartTime + "&end=" + opt.EndTime + "&step=30")
	if err != nil {
		log.Infof("The HTTP query request failed with error %s\n", err)
	} else {
		data, _ := ioutil.ReadAll(response.Body)
		log.Info(string(data))

		// unmarshal the JSON response into a struct (generated using the JSON, using this https://mholt.github.io/json-to-go/
		var fv RangeMetricReponseFromPrometheus
		err0 := json.Unmarshal(data, &fv)
		log.Error(err0)

		metrics := make([]model.MetricSpec, len(fv.Data.Result))

		// now convert to our repsonse struct, so we can marshal it and send out the JSON
		for i, res := range fv.Data.Result {
			//metrics[i].InstanceID = res.Metric.Instance + res.Metric.Device
			//metrics[i].Name = res.Metric.Name
			//metrics[i].MetricValues = make([]*model.Metric, len(res.Values))
			metrics[i].InstanceID = res.Metric.Instance
			metrics[i].Name = res.Metric.Name
			metrics[i].InstanceName = res.Metric.Device
			metricValues := make([]*model.Metric, 0)
			metricValue := &model.Metric{}
			for j := 0; j < len(res.Values); j++ {
				for _, v := range res.Values[j] {
					switch v.(type) {
					case string:
						metricValue.Value, _ = strconv.ParseFloat(v.(string), 64)
					case float64:
						secs := int64(v.(float64))
						metricValue.Timestamp = secs
					default:
						log.Infof("%s is of a type I don't know how to handle", v)
					}

				}
				metricValues = append(metricValues, metricValue)
				metrics[i].MetricValues = metricValues
			}
		}

		bArr, _ := json.Marshal(metrics)
		log.Infof("metrics response json is %s", string(bArr))

		if err != nil {
			log.Error(err)
		}
		return &metrics, err

	}
	return nil, nil
}

func (c *controller) SetDock(dockInfo *model.DockSpec) {
	c.DockInfo = dockInfo
}
<commit_msg>Removing unsused files<commit_after>// Copyright (c) 2019 The OpenSDS Authors All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
This module implements a entry into the OpenSDS metrics controller service.

*/

package metrics

import (
	"encoding/json"
	log "github.com/golang/glog"
	"github.com/opensds/opensds/pkg/dock/client"
	"github.com/opensds/opensds/pkg/model"
	pb "github.com/opensds/opensds/pkg/model/proto"
	"io/ioutil"
	"net/http"
	"strconv"
)

// Controller is an interface for exposing some operations of metric controllers.
type Controller interface {
	GetLatestMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error)
	GetInstantMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error)
	GetRangeMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error)
	SetDock(dockInfo *model.DockSpec)
}

// NewController method creates a controller structure and expose its pointer.
func NewController() Controller {
	return &controller{
		Client: client.NewClient(),
	}
}

type controller struct {
	client.Client
	DockInfo *model.DockSpec
}

// latest+instant metrics structs begin
type InstantMetricReponseFromPrometheus struct {
	Status string `json:"status"`
	Data   Data   `json:"data"`
}
type Metric struct {
	Name       string `json:"__name__"`
	Device     string `json:"device"`
	InstanceID string `json:"instanceID"`
	Job        string `json:"job"`
}
type Result struct {
	Metric Metric        `json:"metric"`
	Value  []interface{} `json:"value"`
}
type Data struct {
	ResultType string   `json:"resultType"`
	Result     []Result `json:"result"`
}

// latest+instant metrics structs end

// latest+range metrics structs begin
type RangeMetricReponseFromPrometheus struct {
	Status string    `json:"status"`
	Data   RangeData `json:"data"`
}
type RangeMetric struct {
	Name     string `json:"__name__"`
	Device   string `json:"device"`
	Instance string `json:"instance"`
	Job      string `json:"job"`
}
type RangeResult struct {
	Metric RangeMetric     `json:"metric"`
	Values [][]interface{} `json:"values"`
}
type RangeData struct {
	ResultType string        `json:"resultType"`
	Result     []RangeResult `json:"result"`
}

// latest+range metrics structs end

func (c *controller) GetLatestMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error) {

	// make a call to Prometheus, convert the response to our format, return
	response, err := http.Get("http://localhost:9090/api/v1/query?query=" + opt.MetricName)
	if err != nil {
		log.Infof("The HTTP query request failed with error %s\n", err)
	} else {
		data, _ := ioutil.ReadAll(response.Body)
		log.Info(string(data))
		// unmarshal the JSON response into a struct (generated using the JSON, using this https://mholt.github.io/json-to-go/
		var fv InstantMetricReponseFromPrometheus
		err0 := json.Unmarshal(data, &fv)
		log.Error(err0)
		metrics := make([]model.MetricSpec, len(fv.Data.Result))

		// now convert to our repsonse struct, so we can marshal it and send out the JSON
		for i, res := range fv.Data.Result {
			metrics[i].InstanceID = res.Metric.InstanceID
			metrics[i].Name = res.Metric.Name
			metrics[i].InstanceName = res.Metric.Device
			metricValues := make([]*model.Metric, 0)
			metricValue := &model.Metric{}
			for _, v := range res.Value {

				switch v.(type) {
				case string:
					metricValue.Value, err = strconv.ParseFloat(v.(string), 64)

					//metricValues = append(metricValues, metricValue)
					//metrics[i].MetricValues[j].Value, _ = strconv.ParseFloat(v.(string), 64)
				case float64:
					secs := int64(v.(float64))
					metricValue.Timestamp = secs
					//metrics[i].MetricValues[j].Timestamp = secs
				default:
					log.Info(v, "is of a type I don't know how to handle")
				}

			}
			metricValues = append(metricValues, metricValue)
			metrics[i].MetricValues = metricValues
		}

		bArr, _ := json.Marshal(metrics)
		log.Infof("metrics response json is %s", string(bArr))

		if err != nil {
			log.Error(err)
		}
		return &metrics, err

	}
	return nil, err
}

func (c *controller) GetInstantMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error) {

	// make a call to Prometheus, convert the response to our format, return
	response, err := http.Get("http://localhost:9090/api/v1/query?query=" + opt.MetricName + "&time=" + opt.StartTime)
	if err != nil {
		log.Infof("The HTTP query request failed with error %s\n", err)
	} else {
		data, _ := ioutil.ReadAll(response.Body)
		log.Infof("response data is %s", string(data))
		// unmarshal the JSON response into a struct (generated using the JSON, using this https://mholt.github.io/json-to-go/
		var fv InstantMetricReponseFromPrometheus
		err0 := json.Unmarshal(data, &fv)
		log.Error(err0)
		metrics := make([]model.MetricSpec, len(fv.Data.Result))

		// now convert to our repsonse struct, so we can marshal it and send out the JSON
		for i, res := range fv.Data.Result {
			metrics[i].InstanceID = res.Metric.InstanceID
			metrics[i].Name = res.Metric.Name
			metrics[i].InstanceName = res.Metric.Device
			metricValues := make([]*model.Metric, 0)
			metricValue := &model.Metric{}
			for _, v := range res.Value {

				switch v.(type) {
				case string:
					metricValue.Value, err = strconv.ParseFloat(v.(string), 64)

					//metricValues = append(metricValues, metricValue)
					//metrics[i].MetricValues[j].Value, _ = strconv.ParseFloat(v.(string), 64)
				case float64:
					secs := int64(v.(float64))
					metricValue.Timestamp = secs
					//metrics[i].MetricValues[j].Timestamp = secs
				default:
					log.Info(v, "is of a type I don't know how to handle")
				}

			}
			metricValues = append(metricValues, metricValue)
			metrics[i].MetricValues = metricValues
		}

		bArr, _ := json.Marshal(metrics)
		log.Infof("metrics response json is %s", string(bArr))

		if err != nil {
			log.Error(err)
		}
		return &metrics, err

	}
	return nil, err
}

func (c *controller) GetRangeMetrics(opt *pb.GetMetricsOpts) (*[]model.MetricSpec, error) {

	//var metrics []model.MetricSpec
	// make a call to Prometheus, convert the response to our format, return
	response, err := http.Get("http://localhost:9090/api/v1/query_range?query=" + opt.MetricName + "&start=" + opt.StartTime + "&end=" + opt.EndTime + "&step=30")
	if err != nil {
		log.Infof("The HTTP query request failed with error %s\n", err)
	} else {
		data, _ := ioutil.ReadAll(response.Body)
		log.Info(string(data))

		// unmarshal the JSON response into a struct (generated using the JSON, using this https://mholt.github.io/json-to-go/
		var fv RangeMetricReponseFromPrometheus
		err0 := json.Unmarshal(data, &fv)
		log.Error(err0)

		metrics := make([]model.MetricSpec, len(fv.Data.Result))

		// now convert to our repsonse struct, so we can marshal it and send out the JSON
		for i, res := range fv.Data.Result {
			metrics[i].InstanceID = res.Metric.Instance
			metrics[i].Name = res.Metric.Name
			metrics[i].InstanceName = res.Metric.Device
			metricValues := make([]*model.Metric, 0)
			metricValue := &model.Metric{}
			for j := 0; j < len(res.Values); j++ {
				for _, v := range res.Values[j] {
					switch v.(type) {
					case string:
						metricValue.Value, _ = strconv.ParseFloat(v.(string), 64)
					case float64:
						secs := int64(v.(float64))
						metricValue.Timestamp = secs
					default:
						log.Infof("%s is of a type I don't know how to handle", v)
					}

				}
				metricValues = append(metricValues, metricValue)
				metrics[i].MetricValues = metricValues
			}
		}

		bArr, _ := json.Marshal(metrics)
		log.Infof("metrics response json is %s", string(bArr))

		if err != nil {
			log.Error(err)
		}
		return &metrics, err

	}
	return nil, nil
}

func (c *controller) SetDock(dockInfo *model.DockSpec) {
	c.DockInfo = dockInfo
}
<|endoftext|>


<commit_before>// Copyright 2017 The etcd-operator Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controller

import (
	api "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2"

	"github.com/sirupsen/logrus"
)

const (
	// Copy from deployment_controller.go:
	// maxRetries is the number of times a etcd backup will be retried before it is dropped out of the queue.
	// With the current rate-limiter in use (5ms*2^(maxRetries-1)) the following numbers represent the times
	// an etcd backup is going to be requeued:
	//
	// 5ms, 10ms, 20ms, 40ms, 80ms, 160ms, 320ms, 640ms, 1.3s, 2.6s, 5.1s, 10.2s, 20.4s, 41s, 82s
	maxRetries = 15
)

func (b *Backup) runWorker() {
	for b.processNextItem() {
	}
}

func (b *Backup) processNextItem() bool {
	// Wait until there is a new item in the working queue
	key, quit := b.queue.Get()
	if quit {
		return false
	}
	// Tell the queue that we are done with processing this key. This unblocks the key for other workers
	// This allows safe parallel processing because two pods with the same key are never processed in
	// parallel.
	defer b.queue.Done(key)
	err := b.processItem(key.(string))
	// Handle the error if something went wrong during the execution of the business logic
	b.handleErr(err, key)
	return true
}

func (b *Backup) processItem(key string) error {
	obj, exists, err := b.indexer.GetByKey(key)
	if err != nil {
		return err
	}
	if !exists {
		return nil
	}

	eb := obj.(*api.EtcdBackup)
	return b.handleBackup(&eb.Spec)
}

func (b *Backup) handleErr(err error, key interface{}) {
	if err == nil {
		// Forget about the #AddRateLimited history of the key on every successful synchronization.
		// This ensures that future processing of updates for this key is not delayed because of
		// an outdated error history.
		b.queue.Forget(key)
		return
	}

	// This controller retries maxRetries times if something goes wrong. After that, it stops trying.
	if b.queue.NumRequeues(key) < maxRetries {
		b.logger.Errorf("error syncing etcd backup (%v): %v", key, err)

		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// queue and the re-enqueue history, the key will be processed later again.
		b.queue.AddRateLimited(key)
		return
	}

	b.queue.Forget(key)
	// Report that, even after several retries, we could not successfully process this key
	b.logger.Infof("Dropping etcd backup (%v) out of the queue: %v", key, err)
}

func (b *Backup) handleBackup(spec *api.EtcdBackupSpec) error {
	switch spec.StorageType {
	case api.BackupStorageTypeS3:
		return handleS3(b.kubecli, spec.S3, b.namespace, spec.ClusterName)
	default:
		logrus.Fatalf("unknown StorageType: %v", spec.StorageType)
	}
	return nil
}
<commit_msg>etcd-backup-operator/controller: add reportBackupStatus<commit_after>// Copyright 2017 The etcd-operator Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controller

import (
	api "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2"

	"github.com/sirupsen/logrus"
)

const (
	// Copy from deployment_controller.go:
	// maxRetries is the number of times a etcd backup will be retried before it is dropped out of the queue.
	// With the current rate-limiter in use (5ms*2^(maxRetries-1)) the following numbers represent the times
	// an etcd backup is going to be requeued:
	//
	// 5ms, 10ms, 20ms, 40ms, 80ms, 160ms, 320ms, 640ms, 1.3s, 2.6s, 5.1s, 10.2s, 20.4s, 41s, 82s
	maxRetries = 15
)

func (b *Backup) runWorker() {
	for b.processNextItem() {
	}
}

func (b *Backup) processNextItem() bool {
	// Wait until there is a new item in the working queue
	key, quit := b.queue.Get()
	if quit {
		return false
	}
	// Tell the queue that we are done with processing this key. This unblocks the key for other workers
	// This allows safe parallel processing because two pods with the same key are never processed in
	// parallel.
	defer b.queue.Done(key)
	err := b.processItem(key.(string))
	// Handle the error if something went wrong during the execution of the business logic
	b.handleErr(err, key)
	return true
}

func (b *Backup) processItem(key string) error {
	obj, exists, err := b.indexer.GetByKey(key)
	if err != nil {
		return err
	}
	if !exists {
		return nil
	}

	eb := obj.(*api.EtcdBackup)
	err = b.handleBackup(&eb.Spec)
	// Report backup status
	b.reportBackupStatus(err, eb)
	return err
}

func (b *Backup) reportBackupStatus(err error, eb *api.EtcdBackup) {
	if err != nil {
		eb.Status.Succeeded = false
		eb.Status.Reason = err.Error()
	} else {
		eb.Status.Succeeded = true
	}
	_, err = b.backupCRCli.EtcdV1beta2().EtcdBackups(b.namespace).Update(eb)
	if err != nil {
		b.logger.Warningf("failed to update status of backup CR %v : (%v)", eb.Name, err)
	}
}

func (b *Backup) handleErr(err error, key interface{}) {
	if err == nil {
		// Forget about the #AddRateLimited history of the key on every successful synchronization.
		// This ensures that future processing of updates for this key is not delayed because of
		// an outdated error history.
		b.queue.Forget(key)
		return
	}

	// This controller retries maxRetries times if something goes wrong. After that, it stops trying.
	if b.queue.NumRequeues(key) < maxRetries {
		b.logger.Errorf("error syncing etcd backup (%v): %v", key, err)

		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// queue and the re-enqueue history, the key will be processed later again.
		b.queue.AddRateLimited(key)
		return
	}

	b.queue.Forget(key)
	// Report that, even after several retries, we could not successfully process this key
	b.logger.Infof("Dropping etcd backup (%v) out of the queue: %v", key, err)
}

func (b *Backup) handleBackup(spec *api.EtcdBackupSpec) error {
	switch spec.StorageType {
	case api.BackupStorageTypeS3:
		return handleS3(b.kubecli, spec.S3, b.namespace, spec.ClusterName)
	default:
		logrus.Fatalf("unknown StorageType: %v", spec.StorageType)
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package machine

import (
	"crypto"
	"fmt"
	"io/ioutil"
	"os"
	"runtime"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/jimmidyson/go-download"
	"k8s.io/minikube/pkg/minikube/assets"
	"k8s.io/minikube/pkg/minikube/bootstrapper"
	"k8s.io/minikube/pkg/minikube/command"
)

type copyFailRunner struct {
	command.Runner
}

func (copyFailRunner) Copy(a assets.CopyableFile) error {
	return fmt.Errorf("test error during copy file")
}

func newFakeCommandRunnerCopyFail() command.Runner {
	return copyFailRunner{command.NewFakeCommandRunner()}
}

func TestCopyBinary(t *testing.T) {
	var tc = []struct {
		lastUpdateCheckFilePath string
		src, dst, desc          string
		err                     bool
		runner                  command.Runner
	}{
		{
			desc:   "not existing src",
			dst:    "/tmp/testCopyBinary1",
			src:    "/tmp/testCopyBinary2",
			err:    true,
			runner: command.NewFakeCommandRunner(),
		},
		{
			desc:   "src /etc/hosts",
			dst:    "/tmp/testCopyBinary1",
			src:    "/etc/hosts",
			err:    false,
			runner: command.NewFakeCommandRunner(),
		},
		{
			desc:   "existing src, copy fail",
			dst:    "/etc/passwd",
			src:    "/etc/hosts",
			err:    true,
			runner: newFakeCommandRunnerCopyFail(),
		},
	}
	for _, test := range tc {
		t.Run(test.desc, func(t *testing.T) {
			err := CopyBinary(test.runner, test.src, test.dst)
			if err != nil && !test.err {
				t.Fatalf("Error %v expected but not occurred", err)
			}
			if err == nil && test.err {
				t.Fatal("Unexpected error")
			}
		})
	}
}

func TestCacheBinariesForBootstrapper(t *testing.T) {
	oldMinikubeHome := os.Getenv("MINIKUBE_HOME")
	defer os.Setenv("MINIKUBE_HOME", oldMinikubeHome)

	minikubeHome, err := ioutil.TempDir("/tmp", "")
	if err != nil {
		t.Fatalf("error during creating tmp dir: %v", err)
	}
	defer os.RemoveAll(minikubeHome)

	var tc = []struct {
		version, clusterBootstrapper string
		minikubeHome                 string
		err                          bool
	}{
		{
			version:             "v1.16.0",
			clusterBootstrapper: bootstrapper.Kubeadm,
			err:                 false,
			minikubeHome:        minikubeHome,
		},
		{
			version:             "invalid version",
			clusterBootstrapper: bootstrapper.Kubeadm,
			err:                 true,
			minikubeHome:        minikubeHome,
		},
	}
	for _, test := range tc {
		t.Run(test.version, func(t *testing.T) {
			os.Setenv("MINIKUBE_HOME", test.minikubeHome)
			err := CacheBinariesForBootstrapper(test.version, test.clusterBootstrapper)
			if err != nil && !test.err {
				t.Fatalf("Got unexpected error %v", err)
			}
			if err == nil && test.err {
				t.Fatalf("Expected error but got %v", err)
			}
		})
	}
}
func TestCacheBinary(t *testing.T) {
	oldMinikubeHome := os.Getenv("MINIKUBE_HOME")
	defer os.Setenv("MINIKUBE_HOME", oldMinikubeHome)

	minikubeHome, err := ioutil.TempDir("/tmp", "")
	if err != nil {
		t.Fatalf("error during creating tmp dir: %v", err)
	}
	defer os.RemoveAll(minikubeHome)
	noWritePermDir, err := ioutil.TempDir("/tmp", "")
	if err != nil {
		t.Fatalf("error during creating tmp dir: %v", err)
	}
	defer os.RemoveAll(noWritePermDir)
	err = os.Chmod(noWritePermDir, 0000)
	if err != nil {
		t.Fatalf("error (%v) during changing permissions of dir %v", err, noWritePermDir)
	}

	var tc = []struct {
		desc, version, osName, archName   string
		minikubeHome, binary, description string
		err                               bool
	}{
		{
			desc:         "ok kubeadm",
			version:      "v1.16.0",
			osName:       "linux",
			archName:     runtime.GOARCH,
			binary:       "kubeadm",
			err:          false,
			minikubeHome: minikubeHome,
		},
		{
			desc:         "minikube home in dir without perms and arm runtime",
			version:      "v1.16.0",
			osName:       runtime.GOOS,
			archName:     "arm",
			binary:       "kubectl",
			err:          true,
			minikubeHome: noWritePermDir,
		},
		{
			desc:         "minikube home in dir without perms",
			version:      "v1.16.0",
			osName:       runtime.GOOS,
			archName:     runtime.GOARCH,
			binary:       "kubectl",
			err:          true,
			minikubeHome: noWritePermDir,
		},
		{
			desc:         "binary foo",
			version:      "v1.16.0",
			osName:       runtime.GOOS,
			archName:     runtime.GOARCH,
			binary:       "foo",
			err:          true,
			minikubeHome: minikubeHome,
		},
		{
			desc:         "version 9000",
			version:      "v9000",
			osName:       runtime.GOOS,
			archName:     runtime.GOARCH,
			binary:       "foo",
			err:          true,
			minikubeHome: minikubeHome,
		},
		{
			desc:         "bad os",
			version:      "v1.16.0",
			osName:       "no-such-os",
			archName:     runtime.GOARCH,
			binary:       "kubectl",
			err:          true,
			minikubeHome: minikubeHome,
		},
	}
	for _, test := range tc {
		t.Run(test.desc, func(t *testing.T) {
			os.Setenv("MINIKUBE_HOME", test.minikubeHome)
			_, err := CacheBinary(test.binary, test.version, test.osName, test.archName)
			if err != nil && !test.err {
				t.Fatalf("Got unexpected error %v", err)
			}
			if err == nil && test.err {
				t.Fatalf("Expected error but got %v", err)
			}
		})
	}
}

func TestDownloadOptions(t *testing.T) {
	var tc = []struct {
		url     string
		version string
		want    download.FileOptions
	}{
		{
			url:     "https://s/kubernetes-release/release/v1.16.0/bin/amd64/kubectl",
			version: "v1.16.0",
			want: download.FileOptions{
				download.Options{
					Checksum:     "https://s/kubernetes-release/release/v1.16.0/bin/amd64/kubectl.sha1",
					ChecksumHash: crypto.SHA1,
				},
				download.MkdirAll,
			},
		},
		{
			url:     "https://s/kubernetes-release/release/v1.10.0/bin/hp9k/kubeadm",
			version: "v1.10.0",
			want: download.FileOptions{
				download.Options{
					Checksum:     "https://s/kubernetes-release/release/v1.10.0/bin/hp9k/kubeadm.sha1",
					ChecksumHash: crypto.SHA1,
				},
				download.MkdirAll,
			},
		},
		{
			url:     "https://s/kubernetes-release/release/v1.18.0/bin/arm64/kubelet",
			version: "v1.18.0",
			want: download.FileOptions{
				download.Options{
					Checksum:     "https://s/kubernetes-release/release/v1.18.0/bin/arm64/kubelet.sha256",
					ChecksumHash: crypto.SHA256,
				},
				download.MkdirAll,
			},
		},
	}
	for _, test := range tc {
		t.Run(test.version, func(t *testing.T) {
			got, err := downloadOptions(test.url, test.version)
			if err != nil {
				t.Fatalf("unexpected error %v", err)
			}

			if diff := cmp.Diff(test.want, got); diff != "" {
				t.Errorf("unexpected options(-want +got):\n%s", diff)
			}
		})
	}
}
<commit_msg>Address lint error<commit_after>/*
Copyright 2019 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package machine

import (
	"crypto"
	"fmt"
	"io/ioutil"
	"os"
	"runtime"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/jimmidyson/go-download"
	"k8s.io/minikube/pkg/minikube/assets"
	"k8s.io/minikube/pkg/minikube/bootstrapper"
	"k8s.io/minikube/pkg/minikube/command"
)

type copyFailRunner struct {
	command.Runner
}

func (copyFailRunner) Copy(a assets.CopyableFile) error {
	return fmt.Errorf("test error during copy file")
}

func newFakeCommandRunnerCopyFail() command.Runner {
	return copyFailRunner{command.NewFakeCommandRunner()}
}

func TestCopyBinary(t *testing.T) {
	var tc = []struct {
		lastUpdateCheckFilePath string
		src, dst, desc          string
		err                     bool
		runner                  command.Runner
	}{
		{
			desc:   "not existing src",
			dst:    "/tmp/testCopyBinary1",
			src:    "/tmp/testCopyBinary2",
			err:    true,
			runner: command.NewFakeCommandRunner(),
		},
		{
			desc:   "src /etc/hosts",
			dst:    "/tmp/testCopyBinary1",
			src:    "/etc/hosts",
			err:    false,
			runner: command.NewFakeCommandRunner(),
		},
		{
			desc:   "existing src, copy fail",
			dst:    "/etc/passwd",
			src:    "/etc/hosts",
			err:    true,
			runner: newFakeCommandRunnerCopyFail(),
		},
	}
	for _, test := range tc {
		t.Run(test.desc, func(t *testing.T) {
			err := CopyBinary(test.runner, test.src, test.dst)
			if err != nil && !test.err {
				t.Fatalf("Error %v expected but not occurred", err)
			}
			if err == nil && test.err {
				t.Fatal("Unexpected error")
			}
		})
	}
}

func TestCacheBinariesForBootstrapper(t *testing.T) {
	oldMinikubeHome := os.Getenv("MINIKUBE_HOME")
	defer os.Setenv("MINIKUBE_HOME", oldMinikubeHome)

	minikubeHome, err := ioutil.TempDir("/tmp", "")
	if err != nil {
		t.Fatalf("error during creating tmp dir: %v", err)
	}
	defer os.RemoveAll(minikubeHome)

	var tc = []struct {
		version, clusterBootstrapper string
		minikubeHome                 string
		err                          bool
	}{
		{
			version:             "v1.16.0",
			clusterBootstrapper: bootstrapper.Kubeadm,
			err:                 false,
			minikubeHome:        minikubeHome,
		},
		{
			version:             "invalid version",
			clusterBootstrapper: bootstrapper.Kubeadm,
			err:                 true,
			minikubeHome:        minikubeHome,
		},
	}
	for _, test := range tc {
		t.Run(test.version, func(t *testing.T) {
			os.Setenv("MINIKUBE_HOME", test.minikubeHome)
			err := CacheBinariesForBootstrapper(test.version, test.clusterBootstrapper)
			if err != nil && !test.err {
				t.Fatalf("Got unexpected error %v", err)
			}
			if err == nil && test.err {
				t.Fatalf("Expected error but got %v", err)
			}
		})
	}
}
func TestCacheBinary(t *testing.T) {
	oldMinikubeHome := os.Getenv("MINIKUBE_HOME")
	defer os.Setenv("MINIKUBE_HOME", oldMinikubeHome)

	minikubeHome, err := ioutil.TempDir("/tmp", "")
	if err != nil {
		t.Fatalf("error during creating tmp dir: %v", err)
	}
	defer os.RemoveAll(minikubeHome)
	noWritePermDir, err := ioutil.TempDir("/tmp", "")
	if err != nil {
		t.Fatalf("error during creating tmp dir: %v", err)
	}
	defer os.RemoveAll(noWritePermDir)
	err = os.Chmod(noWritePermDir, 0000)
	if err != nil {
		t.Fatalf("error (%v) during changing permissions of dir %v", err, noWritePermDir)
	}

	var tc = []struct {
		desc, version, osName, archName   string
		minikubeHome, binary, description string
		err                               bool
	}{
		{
			desc:         "ok kubeadm",
			version:      "v1.16.0",
			osName:       "linux",
			archName:     runtime.GOARCH,
			binary:       "kubeadm",
			err:          false,
			minikubeHome: minikubeHome,
		},
		{
			desc:         "minikube home in dir without perms and arm runtime",
			version:      "v1.16.0",
			osName:       runtime.GOOS,
			archName:     "arm",
			binary:       "kubectl",
			err:          true,
			minikubeHome: noWritePermDir,
		},
		{
			desc:         "minikube home in dir without perms",
			version:      "v1.16.0",
			osName:       runtime.GOOS,
			archName:     runtime.GOARCH,
			binary:       "kubectl",
			err:          true,
			minikubeHome: noWritePermDir,
		},
		{
			desc:         "binary foo",
			version:      "v1.16.0",
			osName:       runtime.GOOS,
			archName:     runtime.GOARCH,
			binary:       "foo",
			err:          true,
			minikubeHome: minikubeHome,
		},
		{
			desc:         "version 9000",
			version:      "v9000",
			osName:       runtime.GOOS,
			archName:     runtime.GOARCH,
			binary:       "foo",
			err:          true,
			minikubeHome: minikubeHome,
		},
		{
			desc:         "bad os",
			version:      "v1.16.0",
			osName:       "no-such-os",
			archName:     runtime.GOARCH,
			binary:       "kubectl",
			err:          true,
			minikubeHome: minikubeHome,
		},
	}
	for _, test := range tc {
		t.Run(test.desc, func(t *testing.T) {
			os.Setenv("MINIKUBE_HOME", test.minikubeHome)
			_, err := CacheBinary(test.binary, test.version, test.osName, test.archName)
			if err != nil && !test.err {
				t.Fatalf("Got unexpected error %v", err)
			}
			if err == nil && test.err {
				t.Fatalf("Expected error but got %v", err)
			}
		})
	}
}

func TestDownloadOptions(t *testing.T) {
	var tc = []struct {
		url     string
		version string
		want    download.FileOptions
	}{
		{
			url:     "https://s/kubernetes-release/release/v1.16.0/bin/amd64/kubectl",
			version: "v1.16.0",
			want: download.FileOptions{
				Options: download.Options{
					Checksum:     "https://s/kubernetes-release/release/v1.16.0/bin/amd64/kubectl.sha1",
					ChecksumHash: crypto.SHA1,
				},
				Mkdirs: download.MkdirAll,
			},
		},
		{
			url:     "https://s/kubernetes-release/release/v1.10.0/bin/hp9k/kubeadm",
			version: "v1.10.0",
			want: download.FileOptions{
				Options: download.Options{
					Checksum:     "https://s/kubernetes-release/release/v1.10.0/bin/hp9k/kubeadm.sha1",
					ChecksumHash: crypto.SHA1,
				},
				Mkdirs: download.MkdirAll,
			},
		},
		{
			url:     "https://s/kubernetes-release/release/v1.18.0/bin/arm64/kubelet",
			version: "v1.18.0",
			want: download.FileOptions{
				Options: download.Options{
					Checksum:     "https://s/kubernetes-release/release/v1.18.0/bin/arm64/kubelet.sha256",
					ChecksumHash: crypto.SHA256,
				},
				Mkdirs: download.MkdirAll,
			},
		},
	}
	for _, test := range tc {
		t.Run(test.version, func(t *testing.T) {
			got, err := downloadOptions(test.url, test.version)
			if err != nil {
				t.Fatalf("unexpected error %v", err)
			}

			if diff := cmp.Diff(test.want, got); diff != "" {
				t.Errorf("unexpected options(-want +got):\n%s", diff)
			}
		})
	}
}
<|endoftext|>


<commit_before>// @author Couchbase <info@couchbase.com>
// @copyright 2014 NorthScale, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package repository

import (
	"github.com/couchbase/gometa/common"
	fdb "github.com/couchbaselabs/goforestdb"
	"log"
	"sync"
)

/////////////////////////////////////////////////////////////////////////////
// Repository
/////////////////////////////////////////////////////////////////////////////

type Repository struct {
	db    *fdb.Database
	mutex sync.Mutex
}

type RepoIterator struct {
	iter *fdb.Iterator
}

/////////////////////////////////////////////////////////////////////////////
// Repository Public Function
/////////////////////////////////////////////////////////////////////////////

//
// Open a repository
//
func OpenRepository() (*Repository, error) {
	return OpenRepositoryWithName(common.REPOSITORY_NAME)
}

func OpenRepositoryWithName(name string) (*Repository, error) {

	config := fdb.DefaultConfig()
	config.SetBufferCacheSize(1024 * 1024)
	db, err := fdb.Open(name, config)
	if err != nil {
		return nil, err
	}

	repo := &Repository{db: db}
	return repo, nil
}

//
// Update/Insert into the repository
//
func (r *Repository) Set(key string, content []byte) error {

	r.mutex.Lock()
	defer r.mutex.Unlock()

	log.Printf("Repo.Set(): key %s, len(content) %d", key, len(content))

	//convert key to its collatejson encoded byte representation
	k, err := CollateString(key)
	if err != nil {
		return err
	}

	// set value
	err = r.db.SetKV(k, content)
	if err != nil {
		return err
	}

	return r.db.Commit(fdb.COMMIT_NORMAL)
}

//
// Retrieve from repository
//
func (r *Repository) Get(key string) ([]byte, error) {

	//convert key to its collatejson encoded byte representation
	k, err := CollateString(key)
	if err != nil {
		return nil, err
	}

	value, err := r.db.GetKV(k)
	log.Printf("Repo.Get(): key %s, found=%v", key, err == nil)
	return value, err
}

//
// Delete from repository
//
func (r *Repository) Delete(key string) error {

	r.mutex.Lock()
	defer r.mutex.Unlock()

	//convert key to its collatejson encoded byte representation
	k, err := CollateString(key)
	if err != nil {
		return err
	}

	err = r.db.DeleteKV(k)
	if err != nil {
		return err
	}

	return r.db.Commit(fdb.COMMIT_NORMAL)
}

//
// Close repository.
//
func (r *Repository) Close() {
	// TODO: Does it need mutex?
	if r.db != nil {
		r.db.Close()
		r.db = nil
	}
}

/////////////////////////////////////////////////////////////////////////////
// RepoIterator Public Function
/////////////////////////////////////////////////////////////////////////////

//
// Create a new iterator.  EndKey is inclusive.
//
func (r *Repository) NewIterator(startKey, endKey string) (*RepoIterator, error) {
	// TODO: Check if fdb is closed.

	k1, err := CollateString(startKey)
	if err != nil {
		return nil, err
	}

	k2, err := CollateString(endKey)
	if err != nil {
		return nil, err
	}

	iter, err := r.db.IteratorInit(k1, k2, fdb.ITR_NONE)
	if err != nil {
		return nil, err
	}
	result := &RepoIterator{iter: iter}
	return result, nil
}

// Get value from iterator
func (i *RepoIterator) Next() (key string, content []byte, err error) {

	// TODO: Check if fdb and iterator is closed
	doc, err := i.iter.Next()
	if err != nil {
		return "", nil, err
	}

	key = DecodeString(doc.Key())
	body := doc.Body()

	return key, body, nil
}

// close iterator
func (i *RepoIterator) Close() {
	// TODO: Check if fdb iterator is closed
	i.iter.Close()
}

// This only support ascii.
func CollateString(key string) ([]byte, error) {
	if key == "" {
		return nil, nil
	}

	return ([]byte)(key), nil
}

func DecodeString(data []byte) string {
	return string(data)
}
<commit_msg>Adapt to goforestdb API changes.<commit_after>// @author Couchbase <info@couchbase.com>
// @copyright 2014 NorthScale, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package repository

import (
	"github.com/couchbase/gometa/common"
	fdb "github.com/couchbaselabs/goforestdb"
	"log"
	"sync"
)

/////////////////////////////////////////////////////////////////////////////
// Repository
/////////////////////////////////////////////////////////////////////////////

type Repository struct {
	dbfile *fdb.File
	db     *fdb.KVStore
	mutex  sync.Mutex
}

type RepoIterator struct {
	iter *fdb.Iterator
}

/////////////////////////////////////////////////////////////////////////////
// Repository Public Function
/////////////////////////////////////////////////////////////////////////////

//
// Open a repository
//
func OpenRepository() (*Repository, error) {
	return OpenRepositoryWithName(common.REPOSITORY_NAME)
}

func OpenRepositoryWithName(name string) (*Repository, error) {

	config := fdb.DefaultConfig()
	config.SetBufferCacheSize(1024 * 1024)
	dbfile, err := fdb.Open(name, config)
	if err != nil {
		return nil, err
	}

	cleanup := common.NewCleanup(func() {
		dbfile.Close()
	})
	defer cleanup.Run()

	db, err := dbfile.OpenKVStoreDefault(nil)
	if err != nil {
		return nil, err
	}
	cleanup.Cancel()

	repo := &Repository{dbfile: dbfile, db: db}
	return repo, nil
}

//
// Update/Insert into the repository
//
func (r *Repository) Set(key string, content []byte) error {

	r.mutex.Lock()
	defer r.mutex.Unlock()

	log.Printf("Repo.Set(): key %s, len(content) %d", key, len(content))

	//convert key to its collatejson encoded byte representation
	k, err := CollateString(key)
	if err != nil {
		return err
	}

	// set value
	err = r.db.SetKV(k, content)
	if err != nil {
		return err
	}

	return r.dbfile.Commit(fdb.COMMIT_NORMAL)
}

//
// Retrieve from repository
//
func (r *Repository) Get(key string) ([]byte, error) {

	//convert key to its collatejson encoded byte representation
	k, err := CollateString(key)
	if err != nil {
		return nil, err
	}

	value, err := r.db.GetKV(k)
	log.Printf("Repo.Get(): key %s, found=%v", key, err == nil)
	return value, err
}

//
// Delete from repository
//
func (r *Repository) Delete(key string) error {

	r.mutex.Lock()
	defer r.mutex.Unlock()

	//convert key to its collatejson encoded byte representation
	k, err := CollateString(key)
	if err != nil {
		return err
	}

	err = r.db.DeleteKV(k)
	if err != nil {
		return err
	}

	return r.dbfile.Commit(fdb.COMMIT_NORMAL)
}

//
// Close repository.
//
func (r *Repository) Close() {
	// TODO: Does it need mutex?
	if r.db != nil {
		r.db.Close()
		r.db = nil

		r.dbfile.Close()
		r.dbfile = nil
	}
}

/////////////////////////////////////////////////////////////////////////////
// RepoIterator Public Function
/////////////////////////////////////////////////////////////////////////////

//
// Create a new iterator.  EndKey is inclusive.
//
func (r *Repository) NewIterator(startKey, endKey string) (*RepoIterator, error) {
	// TODO: Check if fdb is closed.

	k1, err := CollateString(startKey)
	if err != nil {
		return nil, err
	}

	k2, err := CollateString(endKey)
	if err != nil {
		return nil, err
	}

	iter, err := r.db.IteratorInit(k1, k2, fdb.ITR_NONE)
	if err != nil {
		return nil, err
	}
	result := &RepoIterator{iter: iter}
	return result, nil
}

// Get value from iterator
func (i *RepoIterator) Next() (key string, content []byte, err error) {

	// TODO: Check if fdb and iterator is closed
	doc, err := i.iter.Next()
	if err != nil {
		return "", nil, err
	}

	key = DecodeString(doc.Key())
	body := doc.Body()

	return key, body, nil
}

// close iterator
func (i *RepoIterator) Close() {
	// TODO: Check if fdb iterator is closed
	i.iter.Close()
}

// This only support ascii.
func CollateString(key string) ([]byte, error) {
	if key == "" {
		return nil, nil
	}

	return ([]byte)(key), nil
}

func DecodeString(data []byte) string {
	return string(data)
}
<|endoftext|>


<commit_before>package assert

import (
	"fmt"
	"strings"
)

type Line []string

func (line Line) String() string {
	return strings.Join(line, ", ")
}

type Lines []Line

func SliceContains(actual []string, expected Lines, msgAndArgs ...interface{}) bool {
	expectedIndex := 0
	for _, actualValue := range actual {
		allStringsFound := true
		for _, expectedValue := range expected[expectedIndex] {
			allStringsFound = allStringsFound && strings.Contains(strings.ToLower(actualValue), strings.ToLower(expectedValue))
		}

		if allStringsFound {
			expectedIndex++
			if expectedIndex == len(expected) {
				return true
			}
		}
	}
	return Fail(fmt.Sprintf("\"%s\" not found", expected[expectedIndex]), msgAndArgs...)
}

func SliceDoesNotContain(actual []string, expected Lines, msgAndArgs ...interface{}) bool {
	for i, actualValue := range actual {
		for _, expectedLine := range expected {
			allStringsFound := true
			for _, expectedValue := range expectedLine {
				allStringsFound = allStringsFound && strings.Contains(strings.ToLower(actualValue), strings.ToLower(expectedValue))
			}
			if allStringsFound {
				return Fail(fmt.Sprintf("\"%s\" found on line %d", expectedLine, i), msgAndArgs...)
			}
		}
	}
	return true
}
<commit_msg>Improve error message for SliceContains<commit_after>package assert

import (
	"fmt"
	"strings"
)

type Line []string

func (line Line) String() string {
	return strings.Join(line, ", ")
}

type Lines []Line

func SliceContains(actual Line, expected Lines, msgAndArgs ...interface{}) bool {
	expectedIndex := 0
	for _, actualValue := range actual {
		allStringsFound := true
		for _, expectedValue := range expected[expectedIndex] {
			allStringsFound = allStringsFound && strings.Contains(strings.ToLower(actualValue), strings.ToLower(expectedValue))
		}

		if allStringsFound {
			expectedIndex++
			if expectedIndex == len(expected) {
				return true
			}
		}
	}
	return Fail(fmt.Sprintf("\"%s\" not found in actual:\n'%s'\n", expected[expectedIndex], actual), msgAndArgs...)
}

func SliceDoesNotContain(actual Line, expected Lines, msgAndArgs ...interface{}) bool {
	for i, actualValue := range actual {
		for _, expectedLine := range expected {
			allStringsFound := true
			for _, expectedValue := range expectedLine {
				allStringsFound = allStringsFound && strings.Contains(strings.ToLower(actualValue), strings.ToLower(expectedValue))
			}
			if allStringsFound {
				return Fail(fmt.Sprintf("\"%s\" found on line %d", expectedLine, i), msgAndArgs...)
			}
		}
	}
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"archive/tar"
	"compress/gzip"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/pathutil"
)

var (
	gIsDebugMode = false
)

// StepParamsModel ...
type StepParamsModel struct {
	CacheDownloadURL string
	IsDebugMode      bool
}

// CreateStepParamsFromEnvs ...
func CreateStepParamsFromEnvs() (StepParamsModel, error) {
	stepParams := StepParamsModel{
		CacheDownloadURL: os.Getenv("cache_download_url"),
		IsDebugMode:      os.Getenv("is_debug_mode") == "true",
	}

	return stepParams, nil
}

// CacheContentModel ...
type CacheContentModel struct {
	DestinationPath       string `json:"destination_path"`
	RelativePathInArchive string `json:"relative_path_in_archive"`
}

// CacheInfosModel ...
type CacheInfosModel struct {
	Fingerprint string              `json:"fingerprint"`
	Contents    []CacheContentModel `json:"cache_contents"`
}

func exportEnvironmentWithEnvman(keyStr, valueStr string) error {
	envman := exec.Command("envman", "add", "--key", keyStr)
	envman.Stdin = strings.NewReader(valueStr)
	envman.Stdout = os.Stdout
	envman.Stderr = os.Stderr
	return envman.Run()
}

func readCacheInfoFromArchive(archiveFilePth string) (CacheInfosModel, error) {
	f, err := os.Open(archiveFilePth)
	if err != nil {
		return CacheInfosModel{}, fmt.Errorf("Failed to open Archive file (%s): %s", archiveFilePth, err)
	}
	defer func() {
		if err := f.Close(); err != nil {
			log.Printf(" [!] Failed to close Archive file (%s): %s", archiveFilePth, err)
		}
	}()

	gzf, err := gzip.NewReader(f)
	if err != nil {
		return CacheInfosModel{}, fmt.Errorf("Failed to initialize Archive gzip reader: %s", err)
	}
	defer func() {
		if err := gzf.Close(); err != nil {
			log.Printf(" [!] Failed to close Archive gzip reader(%s): %s", archiveFilePth, err)
		}
	}()

	tarReader := tar.NewReader(gzf)
	for {
		header, err := tarReader.Next()
		if err != nil {
			if err == io.EOF {
				break
			}
			return CacheInfosModel{}, fmt.Errorf("Failed to read Archive, Tar error: %s", err)
		}
		filePth := header.Name
		if filePth == "./cache-info.json" {
			var cacheInfos CacheInfosModel
			if err := json.NewDecoder(tarReader).Decode(&cacheInfos); err != nil {
				return CacheInfosModel{}, fmt.Errorf("Failed to read Cache Info JSON from Archive: %s", err)
			}
			return cacheInfos, nil
		}
	}

	return CacheInfosModel{}, errors.New("Did not find the required Cache Info file in the Archive")
}

func uncompressCaches(cacheFilePath string, cacheInfo CacheInfosModel) (string, error) {
	// for _, aCacheContentInfo := range cacheInfo.Contents {
	// 	log.Printf(" * aCacheContentInfo: %#v", aCacheContentInfo)
	// 	tarCmdParams := []string{"-xvzf", cacheFilePath}
	// 	log.Printf(" $ tar %s", tarCmdParams)
	// 	if fullOut, err := cmdex.RunCommandAndReturnCombinedStdoutAndStderr("tar", tarCmdParams...); err != nil {
	// 		log.Printf(" [!] Failed to uncompress cache content item (%#v), full output (stdout & stderr) was: %s", aCacheContentInfo, fullOut)
	// 		return "", fmt.Errorf("Failed to uncompress cache content item, error was: %s", err)
	// 	}
	// }

	tmpCacheInfosDirPath, err := pathutil.NormalizedOSTempDirPath("")
	if err != nil {
		return "", fmt.Errorf(" [!] Failed to create temp directory for cache infos: %s", err)
	}
	if gIsDebugMode {
		log.Printf("=> tmpCacheInfosDirPath: %#v", tmpCacheInfosDirPath)
	}

	// uncompress the archive
	{
		tarCmdParams := []string{"-xvzf", cacheFilePath}
		if gIsDebugMode {
			log.Printf(" $ tar %s", tarCmdParams)
		}
		if fullOut, err := cmdex.RunCommandInDirAndReturnCombinedStdoutAndStderr(tmpCacheInfosDirPath, "tar", tarCmdParams...); err != nil {
			log.Printf(" [!] Failed to uncompress cache archive, full output (stdout & stderr) was: %s", fullOut)
			return "", fmt.Errorf("Failed to uncompress cache archive, error was: %s", err)
		}
	}

	for _, aCacheContentInfo := range cacheInfo.Contents {
		if gIsDebugMode {
			log.Printf(" * aCacheContentInfo: %#v", aCacheContentInfo)
		}
		srcPath := filepath.Join(tmpCacheInfosDirPath, aCacheContentInfo.RelativePathInArchive)
		targetPath := aCacheContentInfo.DestinationPath

		isExist, err := pathutil.IsPathExists(targetPath)
		if err != nil {
			log.Printf(" [!] Failed to check whether target path (%s) exists: %s", targetPath, err)
			continue
		}
		if isExist {
			// use rsync instead of rename
			fileInfo, err := os.Stat(srcPath)
			if err != nil {
				log.Printf(" [!] Failed to get File Info of cache item source (%s): %s", srcPath, err)
				continue
			}

			rsyncSrcPth := filepath.Clean(srcPath)
			rsyncTargetPth := filepath.Clean(targetPath)
			if fileInfo.IsDir() {
				rsyncSrcPth = rsyncSrcPth + "/"
				rsyncTargetPth = rsyncTargetPth + "/"
			}
			rsyncCmdParams := []string{"-avh", rsyncSrcPth, rsyncTargetPth}
			if gIsDebugMode {
				log.Printf(" $ rsync %s", rsyncCmdParams)
			}

			log.Printf(" [RSYNC]: %s => %s", rsyncSrcPth, rsyncTargetPth)
			if fullOut, err := cmdex.RunCommandAndReturnCombinedStdoutAndStderr("rsync", rsyncCmdParams...); err != nil {
				log.Printf(" [!] Failed to rsync cache item (%s) to it's place: %s", srcPath, err)
				log.Printf("     Full output (stdout & stderr) was: %s", fullOut)
				continue
			}
		} else {
			// create required target path
			targetBaseDir := filepath.Dir(targetPath)
			if err := os.MkdirAll(targetBaseDir, 0755); err != nil {
				log.Printf(" [!] Failed to create base path (%s) for cache item (%s): %s", targetBaseDir, srcPath, err)
				continue
			}

			log.Printf(" [MOVE]: %s => %s", srcPath, targetPath)
			if err := os.Rename(srcPath, targetPath); err != nil {
				log.Printf(" [!] Failed to move cache item (%s) to it's place: %s", srcPath, err)
				continue
			}
		}
	}

	return tmpCacheInfosDirPath, nil
}

func downloadFile(url string, localPath string) error {
	out, err := os.Create(localPath)
	if err != nil {
		return fmt.Errorf("Failed to open the local cache file for write: %s", err)
	}
	defer func() {
		if err := out.Close(); err != nil {
			log.Printf(" [!] Failed to close Archive download file (%s): %s", localPath, err)
		}
	}()

	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return fmt.Errorf("Failed to create cache download request: %s", err)
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			log.Printf(" [!] Failed to close Archive download response body: %s", err)
		}
	}()

	if resp.StatusCode != 200 {
		responseBytes, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Printf(" (!) Failed to read response body: %s", err)
		}
		log.Printf(" ==> (!) Response content: %s", responseBytes)
		return fmt.Errorf("Failed to download archive - non success response code: %d", resp.StatusCode)
	}

	// Writer the body to file
	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return fmt.Errorf("Failed to save cache content into file: %s", err)
	}

	return nil
}

func downloadFileWithRetry(url string, localPath string) error {
	if err := downloadFile(url, localPath); err != nil {
		fmt.Println()
		log.Printf(" ===> (!) First download attempt failed, retrying...")
		fmt.Println()
		time.Sleep(3000 * time.Millisecond)
		return downloadFile(url, localPath)
	}
	return nil
}

func main() {
	log.Println("Cache pull...")

	stepParams, err := CreateStepParamsFromEnvs()
	if err != nil {
		log.Fatalf(" [!] Input error : %s", err)
	}
	gIsDebugMode = stepParams.IsDebugMode
	if gIsDebugMode {
		log.Printf("=> stepParams: %#v", stepParams)
	}
	if stepParams.CacheDownloadURL == "" {
		log.Println(" (i) No Cache Download URL specified, there's no cache to use, exiting.")
		return
	}

	//
	// Download Cache Archive
	//

	log.Println("=> Downloading Cache ...")
	cacheTempDir, err := pathutil.NormalizedOSTempDirPath("bitrise-cache")
	if err != nil {
		log.Fatalf(" [!] Failed to create temp directory for cache download: %s", err)
	}
	if gIsDebugMode {
		log.Printf("=> cacheTempDir: %s", cacheTempDir)
	}
	cacheArchiveFilePath := filepath.Join(cacheTempDir, "cache.tar.gz")
	if err := downloadFileWithRetry(stepParams.CacheDownloadURL, cacheArchiveFilePath); err != nil {
		log.Fatalf(" [!] Failed to download cache archive: %s", err)
	}

	if gIsDebugMode {
		log.Printf("=> cacheArchiveFilePath: %s", cacheArchiveFilePath)
	}
	log.Println("=> Downloading Cache [DONE]")

	//
	// Read Cache Info from archive
	//
	cacheInfoFromArchive, err := readCacheInfoFromArchive(cacheArchiveFilePath)
	if err != nil {
		log.Fatalf(" [!] Failed to read from Archive file: %s", err)
	}
	if gIsDebugMode {
		log.Printf("=> cacheInfoFromArchive: %#v", cacheInfoFromArchive)
	}

	//
	// Uncompress cache
	//
	log.Println("=> Uncompressing Cache ...")
	cacheDirPth, err := uncompressCaches(cacheArchiveFilePath, cacheInfoFromArchive)
	if err != nil {
		log.Fatalf(" [!] Failed to uncompress caches: %s", err)
	}
	cacheInfoJSONFilePath := filepath.Join(cacheDirPth, "cache-info.json")
	if isExist, err := pathutil.IsPathExists(cacheInfoJSONFilePath); err != nil {
		log.Fatalf(" [!] Failed to check Cache Info JSON in uncompressed cache data: %s", err)
	} else if !isExist {
		log.Fatalln(" [!] Cache Info JSON not found in uncompressed cache data")
	}
	log.Println("=> Uncompressing Cache [DONE]")

	//
	// Save & expose the Cache Info JSON
	//

	// tmpCacheInfosDirPath, err := pathutil.NormalizedOSTempDirPath("")
	// if err != nil {
	// 	log.Fatalf(" [!] Failed to create temp directory for cache infos: %s", err)
	// }
	// log.Printf("=> tmpCacheInfosDirPath: %#v", tmpCacheInfosDirPath)

	// cacheInfoJSONFilePath := filepath.Join(tmpCacheInfosDirPath, "cache-info.json")
	// jsonBytes, err := json.Marshal(cacheInfoFromArchive)
	// if err != nil {
	// 	log.Fatalf(" [!] Failed to generate Cache Info JSON: %s", err)
	// }

	// if err := fileutil.WriteBytesToFile(cacheInfoJSONFilePath, jsonBytes); err != nil {
	// 	log.Fatalf(" [!] Failed to write Cache Info YML into file (%s): %s", cacheInfoJSONFilePath, err)
	// }

	if err := exportEnvironmentWithEnvman("BITRISE_CACHE_INFO_PATH", cacheInfoJSONFilePath); err != nil {
		log.Fatalf(" [!] Failed to export Cache Info YML path with envman: %s", err)
	}
	if gIsDebugMode {
		log.Printf(" (i) $BITRISE_CACHE_INFO_PATH=%s", cacheInfoJSONFilePath)
	}

	log.Println("=> Finished")
}
<commit_msg>Move cache files: use `mv` instead of Go's `os.Rename`, as `mv` can handle cross-device file move.<commit_after>package main

import (
	"archive/tar"
	"compress/gzip"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/pathutil"
)

var (
	gIsDebugMode = false
)

// StepParamsModel ...
type StepParamsModel struct {
	CacheDownloadURL string
	IsDebugMode      bool
}

// CreateStepParamsFromEnvs ...
func CreateStepParamsFromEnvs() (StepParamsModel, error) {
	stepParams := StepParamsModel{
		CacheDownloadURL: os.Getenv("cache_download_url"),
		IsDebugMode:      os.Getenv("is_debug_mode") == "true",
	}

	return stepParams, nil
}

// CacheContentModel ...
type CacheContentModel struct {
	DestinationPath       string `json:"destination_path"`
	RelativePathInArchive string `json:"relative_path_in_archive"`
}

// CacheInfosModel ...
type CacheInfosModel struct {
	Fingerprint string              `json:"fingerprint"`
	Contents    []CacheContentModel `json:"cache_contents"`
}

func exportEnvironmentWithEnvman(keyStr, valueStr string) error {
	envman := exec.Command("envman", "add", "--key", keyStr)
	envman.Stdin = strings.NewReader(valueStr)
	envman.Stdout = os.Stdout
	envman.Stderr = os.Stderr
	return envman.Run()
}

func readCacheInfoFromArchive(archiveFilePth string) (CacheInfosModel, error) {
	f, err := os.Open(archiveFilePth)
	if err != nil {
		return CacheInfosModel{}, fmt.Errorf("Failed to open Archive file (%s): %s", archiveFilePth, err)
	}
	defer func() {
		if err := f.Close(); err != nil {
			log.Printf(" [!] Failed to close Archive file (%s): %s", archiveFilePth, err)
		}
	}()

	gzf, err := gzip.NewReader(f)
	if err != nil {
		return CacheInfosModel{}, fmt.Errorf("Failed to initialize Archive gzip reader: %s", err)
	}
	defer func() {
		if err := gzf.Close(); err != nil {
			log.Printf(" [!] Failed to close Archive gzip reader(%s): %s", archiveFilePth, err)
		}
	}()

	tarReader := tar.NewReader(gzf)
	for {
		header, err := tarReader.Next()
		if err != nil {
			if err == io.EOF {
				break
			}
			return CacheInfosModel{}, fmt.Errorf("Failed to read Archive, Tar error: %s", err)
		}
		filePth := header.Name
		if filePth == "./cache-info.json" {
			var cacheInfos CacheInfosModel
			if err := json.NewDecoder(tarReader).Decode(&cacheInfos); err != nil {
				return CacheInfosModel{}, fmt.Errorf("Failed to read Cache Info JSON from Archive: %s", err)
			}
			return cacheInfos, nil
		}
	}

	return CacheInfosModel{}, errors.New("Did not find the required Cache Info file in the Archive")
}

func uncompressCaches(cacheFilePath string, cacheInfo CacheInfosModel) (string, error) {
	// for _, aCacheContentInfo := range cacheInfo.Contents {
	// 	log.Printf(" * aCacheContentInfo: %#v", aCacheContentInfo)
	// 	tarCmdParams := []string{"-xvzf", cacheFilePath}
	// 	log.Printf(" $ tar %s", tarCmdParams)
	// 	if fullOut, err := cmdex.RunCommandAndReturnCombinedStdoutAndStderr("tar", tarCmdParams...); err != nil {
	// 		log.Printf(" [!] Failed to uncompress cache content item (%#v), full output (stdout & stderr) was: %s", aCacheContentInfo, fullOut)
	// 		return "", fmt.Errorf("Failed to uncompress cache content item, error was: %s", err)
	// 	}
	// }

	tmpCacheInfosDirPath, err := pathutil.NormalizedOSTempDirPath("")
	if err != nil {
		return "", fmt.Errorf(" [!] Failed to create temp directory for cache infos: %s", err)
	}
	if gIsDebugMode {
		log.Printf("=> tmpCacheInfosDirPath: %#v", tmpCacheInfosDirPath)
	}

	// uncompress the archive
	{
		tarCmdParams := []string{"-xvzf", cacheFilePath}
		if gIsDebugMode {
			log.Printf(" $ tar %s", tarCmdParams)
		}
		if fullOut, err := cmdex.RunCommandInDirAndReturnCombinedStdoutAndStderr(tmpCacheInfosDirPath, "tar", tarCmdParams...); err != nil {
			log.Printf(" [!] Failed to uncompress cache archive, full output (stdout & stderr) was: %s", fullOut)
			return "", fmt.Errorf("Failed to uncompress cache archive, error was: %s", err)
		}
	}

	for _, aCacheContentInfo := range cacheInfo.Contents {
		if gIsDebugMode {
			log.Printf(" * aCacheContentInfo: %#v", aCacheContentInfo)
		}
		srcPath := filepath.Join(tmpCacheInfosDirPath, aCacheContentInfo.RelativePathInArchive)
		targetPath := aCacheContentInfo.DestinationPath

		isExist, err := pathutil.IsPathExists(targetPath)
		if err != nil {
			log.Printf(" [!] Failed to check whether target path (%s) exists: %s", targetPath, err)
			continue
		}
		if isExist {
			// use rsync instead of rename
			fileInfo, err := os.Stat(srcPath)
			if err != nil {
				log.Printf(" [!] Failed to get File Info of cache item source (%s): %s", srcPath, err)
				continue
			}

			rsyncSrcPth := filepath.Clean(srcPath)
			rsyncTargetPth := filepath.Clean(targetPath)
			if fileInfo.IsDir() {
				rsyncSrcPth = rsyncSrcPth + "/"
				rsyncTargetPth = rsyncTargetPth + "/"
			}
			rsyncCmdParams := []string{"-avh", rsyncSrcPth, rsyncTargetPth}
			if gIsDebugMode {
				log.Printf(" $ rsync %s", rsyncCmdParams)
			}

			log.Printf(" [RSYNC]: %s => %s", rsyncSrcPth, rsyncTargetPth)
			if fullOut, err := cmdex.RunCommandAndReturnCombinedStdoutAndStderr("rsync", rsyncCmdParams...); err != nil {
				log.Printf(" [!] Failed to rsync cache item (%s) to it's place (%s): %s", srcPath, targetPath, err)
				log.Printf("     Full output (stdout & stderr) was: %s", fullOut)
				continue
			}
		} else {
			// create required target path
			targetBaseDir := filepath.Dir(targetPath)
			if err := os.MkdirAll(targetBaseDir, 0755); err != nil {
				log.Printf(" [!] Failed to create base path (%s) for cache item (%s): %s", targetBaseDir, srcPath, err)
				continue
			}

			// move the file to its target path

			// NOTE: we use `mv` to move it instead of Go's `os.Rename`,
			//  because `mv` can move files between separate devices/drives, by using copy&delete.
			//  This is primarily an issue on the Docker stacks,
			//  where shared folders are treated as separate devices, and `os.Rename` would fail.
			mvCmdParams := []string{srcPath, targetPath}
			if gIsDebugMode {
				log.Printf(" $ mv %s", mvCmdParams)
			}

			log.Printf(" [MOVE]: %s => %s", srcPath, targetPath)
			if fullOut, err := cmdex.RunCommandAndReturnCombinedStdoutAndStderr("mv", mvCmdParams...); err != nil {
				log.Printf(" [!] Failed to mv cache item (%s) to it's place (%s): %s", srcPath, targetPath, err)
				log.Printf("     Full output (stdout & stderr) was: %s", fullOut)
				continue
			}
			// if err := os.Rename(srcPath, targetPath); err != nil {
			// 	log.Printf(" [!] Failed to move cache item (%s) to it's place: %s", srcPath, err)
			// 	continue
			// }
		}
	}

	return tmpCacheInfosDirPath, nil
}

func downloadFile(url string, localPath string) error {
	out, err := os.Create(localPath)
	if err != nil {
		return fmt.Errorf("Failed to open the local cache file for write: %s", err)
	}
	defer func() {
		if err := out.Close(); err != nil {
			log.Printf(" [!] Failed to close Archive download file (%s): %s", localPath, err)
		}
	}()

	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return fmt.Errorf("Failed to create cache download request: %s", err)
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			log.Printf(" [!] Failed to close Archive download response body: %s", err)
		}
	}()

	if resp.StatusCode != 200 {
		responseBytes, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Printf(" (!) Failed to read response body: %s", err)
		}
		log.Printf(" ==> (!) Response content: %s", responseBytes)
		return fmt.Errorf("Failed to download archive - non success response code: %d", resp.StatusCode)
	}

	// Writer the body to file
	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return fmt.Errorf("Failed to save cache content into file: %s", err)
	}

	return nil
}

func downloadFileWithRetry(url string, localPath string) error {
	if err := downloadFile(url, localPath); err != nil {
		fmt.Println()
		log.Printf(" ===> (!) First download attempt failed, retrying...")
		fmt.Println()
		time.Sleep(3000 * time.Millisecond)
		return downloadFile(url, localPath)
	}
	return nil
}

func main() {
	log.Println("Cache pull...")

	stepParams, err := CreateStepParamsFromEnvs()
	if err != nil {
		log.Fatalf(" [!] Input error : %s", err)
	}
	gIsDebugMode = stepParams.IsDebugMode
	if gIsDebugMode {
		log.Printf("=> stepParams: %#v", stepParams)
	}
	if stepParams.CacheDownloadURL == "" {
		log.Println(" (i) No Cache Download URL specified, there's no cache to use, exiting.")
		return
	}

	//
	// Download Cache Archive
	//

	log.Println("=> Downloading Cache ...")
	cacheTempDir, err := pathutil.NormalizedOSTempDirPath("bitrise-cache")
	if err != nil {
		log.Fatalf(" [!] Failed to create temp directory for cache download: %s", err)
	}
	if gIsDebugMode {
		log.Printf("=> cacheTempDir: %s", cacheTempDir)
	}
	cacheArchiveFilePath := filepath.Join(cacheTempDir, "cache.tar.gz")
	if err := downloadFileWithRetry(stepParams.CacheDownloadURL, cacheArchiveFilePath); err != nil {
		log.Fatalf(" [!] Failed to download cache archive: %s", err)
	}

	if gIsDebugMode {
		log.Printf("=> cacheArchiveFilePath: %s", cacheArchiveFilePath)
	}
	log.Println("=> Downloading Cache [DONE]")

	//
	// Read Cache Info from archive
	//
	cacheInfoFromArchive, err := readCacheInfoFromArchive(cacheArchiveFilePath)
	if err != nil {
		log.Fatalf(" [!] Failed to read from Archive file: %s", err)
	}
	if gIsDebugMode {
		log.Printf("=> cacheInfoFromArchive: %#v", cacheInfoFromArchive)
	}

	//
	// Uncompress cache
	//
	log.Println("=> Uncompressing Cache ...")
	cacheDirPth, err := uncompressCaches(cacheArchiveFilePath, cacheInfoFromArchive)
	if err != nil {
		log.Fatalf(" [!] Failed to uncompress caches: %s", err)
	}
	cacheInfoJSONFilePath := filepath.Join(cacheDirPth, "cache-info.json")
	if isExist, err := pathutil.IsPathExists(cacheInfoJSONFilePath); err != nil {
		log.Fatalf(" [!] Failed to check Cache Info JSON in uncompressed cache data: %s", err)
	} else if !isExist {
		log.Fatalln(" [!] Cache Info JSON not found in uncompressed cache data")
	}
	log.Println("=> Uncompressing Cache [DONE]")

	//
	// Save & expose the Cache Info JSON
	//

	// tmpCacheInfosDirPath, err := pathutil.NormalizedOSTempDirPath("")
	// if err != nil {
	// 	log.Fatalf(" [!] Failed to create temp directory for cache infos: %s", err)
	// }
	// log.Printf("=> tmpCacheInfosDirPath: %#v", tmpCacheInfosDirPath)

	// cacheInfoJSONFilePath := filepath.Join(tmpCacheInfosDirPath, "cache-info.json")
	// jsonBytes, err := json.Marshal(cacheInfoFromArchive)
	// if err != nil {
	// 	log.Fatalf(" [!] Failed to generate Cache Info JSON: %s", err)
	// }

	// if err := fileutil.WriteBytesToFile(cacheInfoJSONFilePath, jsonBytes); err != nil {
	// 	log.Fatalf(" [!] Failed to write Cache Info YML into file (%s): %s", cacheInfoJSONFilePath, err)
	// }

	if err := exportEnvironmentWithEnvman("BITRISE_CACHE_INFO_PATH", cacheInfoJSONFilePath); err != nil {
		log.Fatalf(" [!] Failed to export Cache Info YML path with envman: %s", err)
	}
	if gIsDebugMode {
		log.Printf(" (i) $BITRISE_CACHE_INFO_PATH=%s", cacheInfoJSONFilePath)
	}

	log.Println("=> Finished")
}
<|endoftext|>