<commit_before>package sudoku

import (
	"container/list"
	"fmt"
	"log"
	"strconv"
)

type forcingChainsTechnique struct {
	*basicSolveTechnique
}

func (self *forcingChainsTechnique) HumanLikelihood() float64 {
	//TODO: figure out what the baseDifficulty should be
	return self.difficultyHelper(200.0)
}

func (self *forcingChainsTechnique) Description(step *SolveStep) string {
	//TODO: implement this
	return "ERROR: NOT IMPLEMENTED"
}

func (self *forcingChainsTechnique) Find(grid *Grid, results chan *SolveStep, done chan bool) {
	//TODO: test that this will find multiple if they exist.
	//TODO: Implement this.

	getter := grid.queue().DefaultGetter()

	_MAX_IMPLICATION_STEPS := 6

	for {

		//Check if it's time to stop.
		select {
		case <-done:
			return
		default:
		}

		candidate := getter.GetSmallerThan(3)

		if candidate == nil {
			break
		}

		candidateCell := candidate.(*Cell)

		if len(candidateCell.Possibilities()) != 2 {
			//We found one with 1 possibility, which isn't interesting for us--nakedSingle should do that one.
			continue
		}

		firstPossibilityNum := candidateCell.Possibilities()[0]
		secondPossibilityNum := candidateCell.Possibilities()[1]

		firstGrid := grid.Copy()
		secondGrid := grid.Copy()

		//Check that the neighbor isn't just already having a single possibility, because then this technique is overkill.

		firstAccumulator := chainSearcher(_MAX_IMPLICATION_STEPS,
			candidateCell.InGrid(firstGrid),
			firstPossibilityNum)

		secondAccumulator := chainSearcher(_MAX_IMPLICATION_STEPS,
			candidateCell.InGrid(secondGrid),
			secondPossibilityNum)

		doPrint := candidateCell.Row() == 1 && candidateCell.Col() == 0

		//For these debugging purposes, only print out the candidateCell we know to be interesting in the test case.
		if doPrint {
			log.Println(firstAccumulator)
			log.Println(secondAccumulator)
		}

		//See if either branch, at some generation, has the same cell forced to the same number in either generation.

		for firstGeneration := 0; firstGeneration < len(firstAccumulator); firstGeneration++ {
			for secondGeneration := 0; secondGeneration < len(secondAccumulator); secondGeneration++ {
				firstAffectedCells := firstAccumulator[firstGeneration]
				secondAffectedCells := secondAccumulator[secondGeneration]

				for key, val := range firstAffectedCells {
					//Skip the candidateCell, because that's not a meaningful overlap--we set that one as a way of branching!
					if key == candidateCell.ref() {
						continue
					}

					if num, ok := secondAffectedCells[key]; ok {
						//Found cell overlap! ... is the forced number the same?
						if val == num {
							//Yup, seems like we've found a cell that is forced to the same value on either branch.
							step := &SolveStep{self,
								CellSlice{key.Cell(grid)},
								IntSlice{val},
								CellSlice{candidateCell},
								candidateCell.Possibilities(),
							}

							if doPrint {
								log.Println(step)
							}

							if step.IsUseful(grid) {
								if doPrint {
									log.Println("Found solution on generation: ",
										firstGeneration,
										"+",
										secondGeneration,
										"=",
										firstGeneration+secondGeneration,
										"\n",
									)
								}
								select {
								case results <- step:
								case <-done:
									return
								}
							}
						}
					}
				}
			}
		}

		//TODO: figure out why the tests are coming back with different answers, even when only looking at the key cell
		//that should work from the example.
		//TODO: we should prefer solutions where the total implications on both branches are minimized.
		//For example, if only one implication is requried on left, but 4 are on right, that's preferable to one where
		//three implications are required on both sides.
		//TODO: figure out a way to only compute a generation if required on each branch (don't compute all the way to _MAX_IMPLICATIONS to start)

		//TODO: currently we pass back duplicates, so if we find a cell in generation 5, we will also
		//return the same step in generation 6. Either keep track of cells and don't repeat, or do a
		//better job comparing generations.

		//TODO: ideally steps with a higher generation + generation score
		//would be scored as higher diffiuclty maybe include a
		//difficultyMultiplier in SolveStep that we can fill in? Hmmm, but
		//ideally it would factor  in at humanLikelihood level. Having a
		//million different ForcingChainLength techniques would be a
		//nightmare, peformance wise... unless there was a way to pass the
		//work done in one technique to another.

	}
}

type chainSearcherGenerationDetails map[cellRef]int

func (c chainSearcherGenerationDetails) String() string {
	result := "Begin map (length " + strconv.Itoa(len(c)) + ")\n"
	for cell, num := range c {
		result += "\t" + cell.String() + " : " + strconv.Itoa(num) + "\n"
	}
	result += "End map\n"
	return result
}

type chainSearcherAccumulator []chainSearcherGenerationDetails

func (c chainSearcherAccumulator) String() string {
	result := "Accumulator[\n"
	for _, rec := range c {
		result += fmt.Sprintf("%s\n", rec)
	}
	result += "]\n"
	return result
}

func (c chainSearcherAccumulator) addGeneration() chainSearcherAccumulator {
	newGeneration := make(chainSearcherGenerationDetails)
	result := append(c, newGeneration)
	if len(result) > 1 {
		oldGeneration := result[len(result)-2]
		//Accumulate forward old generation
		for key, val := range oldGeneration {
			newGeneration[key] = val
		}
	}
	return result
}

func chainSearcher(maxGeneration int, cell *Cell, numToApply int) chainSearcherAccumulator {

	//Chainsearcher implements a BFS over implications forward given the starting point.
	//It collects its results in the provided chainSearcherAccumulator.

	//the first time we cross over into a new generation, we should do a one-time copy of the old generation
	//into the new.
	//At any write, if we notice that we'd be overwriting to a different value, we can bail out (how would
	//we mark that we bailed early), since we've run into an inconsistency down this branch and following
	//it further is not useful.

	type modificationToMake struct {
		generation int
		cell       *Cell
		numToApply int
	}

	var result chainSearcherAccumulator

	workSteps := list.New()

	//Add the first workstep.
	workSteps.PushBack(modificationToMake{
		0,
		cell,
		numToApply,
	})

	var step modificationToMake

	e := workSteps.Front()

	for e != nil {

		workSteps.Remove(e)

		switch t := e.Value.(type) {
		case modificationToMake:
			step = t
		default:
			panic("Found unexpected type in workSteps list")
		}

		if step.generation > maxGeneration {
			break
		}

		for len(result) < step.generation+1 {
			result = result.addGeneration()
		}

		generationDetails := result[step.generation]

		cellsToVisit := step.cell.Neighbors().FilterByPossible(step.numToApply).FilterByNumPossibilities(2)

		step.cell.SetNumber(step.numToApply)

		if currentVal, ok := generationDetails[step.cell.ref()]; ok {
			if currentVal != step.numToApply {
				//Found a contradiction! We can bail from processing any more because this branch leads inexorably
				//to a contradiction.

				//However, this last generation--the one we found the inconsistency in--needs to be thrown out.

				return result[:len(result)-1]
			}
		}
		generationDetails[step.cell.ref()] = step.numToApply

		for _, cellToVisit := range cellsToVisit {
			possibilities := cellToVisit.Possibilities()

			if len(possibilities) != 1 {
				panic("Expected the cell to have one possibility")
			}

			forcedNum := possibilities[0]

			//Each branch modifies the grid, so create a new copy
			newGrid := cellToVisit.grid.Copy()
			cellToVisit = cellToVisit.InGrid(newGrid)

			workSteps.PushBack(modificationToMake{
				step.generation + 1,
				cellToVisit,
				forcedNum,
			})

		}

		e = workSteps.Front()

	}

	return result

}
<commit_msg>TESTS FAIL. Made it so we don't vend duplicate steps, mainly just by cheating and keeping a map of already vended cells. Gross.<commit_after>package sudoku

import (
	"container/list"
	"fmt"
	"log"
	"strconv"
)

type forcingChainsTechnique struct {
	*basicSolveTechnique
}

func (self *forcingChainsTechnique) HumanLikelihood() float64 {
	//TODO: figure out what the baseDifficulty should be
	return self.difficultyHelper(200.0)
}

func (self *forcingChainsTechnique) Description(step *SolveStep) string {
	//TODO: implement this
	return "ERROR: NOT IMPLEMENTED"
}

func (self *forcingChainsTechnique) Find(grid *Grid, results chan *SolveStep, done chan bool) {
	//TODO: test that this will find multiple if they exist.
	//TODO: Implement this.

	getter := grid.queue().DefaultGetter()

	_MAX_IMPLICATION_STEPS := 6

	for {

		//Check if it's time to stop.
		select {
		case <-done:
			return
		default:
		}

		candidate := getter.GetSmallerThan(3)

		if candidate == nil {
			break
		}

		candidateCell := candidate.(*Cell)

		if len(candidateCell.Possibilities()) != 2 {
			//We found one with 1 possibility, which isn't interesting for us--nakedSingle should do that one.
			continue
		}

		firstPossibilityNum := candidateCell.Possibilities()[0]
		secondPossibilityNum := candidateCell.Possibilities()[1]

		firstGrid := grid.Copy()
		secondGrid := grid.Copy()

		//Check that the neighbor isn't just already having a single possibility, because then this technique is overkill.

		firstAccumulator := chainSearcher(_MAX_IMPLICATION_STEPS,
			candidateCell.InGrid(firstGrid),
			firstPossibilityNum)

		secondAccumulator := chainSearcher(_MAX_IMPLICATION_STEPS,
			candidateCell.InGrid(secondGrid),
			secondPossibilityNum)

		doPrint := candidateCell.Row() == 1 && candidateCell.Col() == 0

		//For these debugging purposes, only print out the candidateCell we know to be interesting in the test case.
		if doPrint {
			log.Println(firstAccumulator)
			log.Println(secondAccumulator)
		}

		//Cells that we've already vended and shouldn't vend again if we find another
		//TODO: figure out a better way to not vend duplicates. this method feels dirty.
		vendedCells := make(map[cellRef]bool)
		//don't vend the candidateCell; obviously both of the two branches will overlap on that one
		//in generation0.
		vendedCells[candidateCell.ref()] = true

		//See if either branch, at some generation, has the same cell forced to the same number in either generation.

		//TODO: visit the pairs of generations in such a way that the sum of the two generation counts
		//goes up linearly. This might already happen... think harder about it.
		for firstGeneration := 0; firstGeneration < len(firstAccumulator); firstGeneration++ {
			for secondGeneration := 0; secondGeneration < len(secondAccumulator); secondGeneration++ {
				firstAffectedCells := firstAccumulator[firstGeneration]
				secondAffectedCells := secondAccumulator[secondGeneration]

				for key, val := range firstAffectedCells {
					//Skip the candidateCell, because that's not a meaningful overlap--we set that one as a way of branching!

					if _, ok := vendedCells[key]; ok {
						//This is a cell we've already vended
						continue
					}

					if num, ok := secondAffectedCells[key]; ok {
						//Found cell overlap! ... is the forced number the same?
						if val == num {
							//Yup, seems like we've found a cell that is forced to the same value on either branch.
							step := &SolveStep{self,
								CellSlice{key.Cell(grid)},
								IntSlice{val},
								CellSlice{candidateCell},
								candidateCell.Possibilities(),
							}

							if doPrint {
								log.Println(step)
							}

							if step.IsUseful(grid) {
								if doPrint {
									log.Println("Found solution on generation: ",
										firstGeneration,
										"+",
										secondGeneration,
										"=",
										firstGeneration+secondGeneration,
										"\n",
									)
								}
								vendedCells[key] = true
								select {
								case results <- step:
								case <-done:
									return
								}
							}
						}
					}
				}
			}
		}

		//TODO: figure out why the tests are coming back with different answers, even when only looking at the key cell
		//that should work from the example.
		//TODO: we should prefer solutions where the total implications on both branches are minimized.
		//For example, if only one implication is requried on left, but 4 are on right, that's preferable to one where
		//three implications are required on both sides.
		//TODO: figure out a way to only compute a generation if required on each branch (don't compute all the way to _MAX_IMPLICATIONS to start)

		//TODO: ideally steps with a higher generation + generation score
		//would be scored as higher diffiuclty maybe include a
		//difficultyMultiplier in SolveStep that we can fill in? Hmmm, but
		//ideally it would factor  in at humanLikelihood level. Having a
		//million different ForcingChainLength techniques would be a
		//nightmare, peformance wise... unless there was a way to pass the
		//work done in one technique to another.

	}
}

type chainSearcherGenerationDetails map[cellRef]int

func (c chainSearcherGenerationDetails) String() string {
	result := "Begin map (length " + strconv.Itoa(len(c)) + ")\n"
	for cell, num := range c {
		result += "\t" + cell.String() + " : " + strconv.Itoa(num) + "\n"
	}
	result += "End map\n"
	return result
}

type chainSearcherAccumulator []chainSearcherGenerationDetails

func (c chainSearcherAccumulator) String() string {
	result := "Accumulator[\n"
	for _, rec := range c {
		result += fmt.Sprintf("%s\n", rec)
	}
	result += "]\n"
	return result
}

func (c chainSearcherAccumulator) addGeneration() chainSearcherAccumulator {
	newGeneration := make(chainSearcherGenerationDetails)
	result := append(c, newGeneration)
	if len(result) > 1 {
		oldGeneration := result[len(result)-2]
		//Accumulate forward old generation
		for key, val := range oldGeneration {
			newGeneration[key] = val
		}
	}
	return result
}

func chainSearcher(maxGeneration int, cell *Cell, numToApply int) chainSearcherAccumulator {

	//Chainsearcher implements a BFS over implications forward given the starting point.
	//It collects its results in the provided chainSearcherAccumulator.

	//the first time we cross over into a new generation, we should do a one-time copy of the old generation
	//into the new.
	//At any write, if we notice that we'd be overwriting to a different value, we can bail out (how would
	//we mark that we bailed early), since we've run into an inconsistency down this branch and following
	//it further is not useful.

	type modificationToMake struct {
		generation int
		cell       *Cell
		numToApply int
	}

	var result chainSearcherAccumulator

	workSteps := list.New()

	//Add the first workstep.
	workSteps.PushBack(modificationToMake{
		0,
		cell,
		numToApply,
	})

	var step modificationToMake

	e := workSteps.Front()

	for e != nil {

		workSteps.Remove(e)

		switch t := e.Value.(type) {
		case modificationToMake:
			step = t
		default:
			panic("Found unexpected type in workSteps list")
		}

		if step.generation > maxGeneration {
			break
		}

		for len(result) < step.generation+1 {
			result = result.addGeneration()
		}

		generationDetails := result[step.generation]

		cellsToVisit := step.cell.Neighbors().FilterByPossible(step.numToApply).FilterByNumPossibilities(2)

		step.cell.SetNumber(step.numToApply)

		if currentVal, ok := generationDetails[step.cell.ref()]; ok {
			if currentVal != step.numToApply {
				//Found a contradiction! We can bail from processing any more because this branch leads inexorably
				//to a contradiction.

				//However, this last generation--the one we found the inconsistency in--needs to be thrown out.

				return result[:len(result)-1]
			}
		}
		generationDetails[step.cell.ref()] = step.numToApply

		for _, cellToVisit := range cellsToVisit {
			possibilities := cellToVisit.Possibilities()

			if len(possibilities) != 1 {
				panic("Expected the cell to have one possibility")
			}

			forcedNum := possibilities[0]

			//Each branch modifies the grid, so create a new copy
			newGrid := cellToVisit.grid.Copy()
			cellToVisit = cellToVisit.InGrid(newGrid)

			workSteps.PushBack(modificationToMake{
				step.generation + 1,
				cellToVisit,
				forcedNum,
			})

		}

		e = workSteps.Front()

	}

	return result

}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 node

import (
	"fmt"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/uuid"
	kubefeatures "k8s.io/kubernetes/pkg/features"
	"k8s.io/kubernetes/test/e2e/framework"
	e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
	imageutils "k8s.io/kubernetes/test/utils/image"
	admissionapi "k8s.io/pod-security-admission/api"

	"github.com/onsi/ginkgo"
)

var _ = SIGDescribe("Downward API", func() {
	f := framework.NewDefaultFramework("downward-api")
	f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for name, namespace and ip
	   Description: Downward API MUST expose Pod and Container fields as environment variables. Specify Pod Name, namespace and IP as environment variable in the Pod Spec are visible at runtime in the container.
	*/
	framework.ConformanceIt("should provide pod name, namespace and IP address as env vars [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "POD_NAME",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "metadata.name",
					},
				},
			},
			{
				Name: "POD_NAMESPACE",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "metadata.namespace",
					},
				},
			},
			{
				Name: "POD_IP",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "status.podIP",
					},
				},
			},
		}

		expectations := []string{
			fmt.Sprintf("POD_NAME=%v", podName),
			fmt.Sprintf("POD_NAMESPACE=%v", f.Namespace.Name),
			fmt.Sprintf("POD_IP=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6),
		}

		testDownwardAPI(f, podName, env, expectations)
	})

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for host ip
	   Description: Downward API MUST expose Pod and Container fields as environment variables. Specify host IP as environment variable in the Pod Spec are visible at runtime in the container.
	*/
	framework.ConformanceIt("should provide host IP as an env var [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "HOST_IP",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "status.hostIP",
					},
				},
			},
		}

		expectations := []string{
			fmt.Sprintf("HOST_IP=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6),
		}

		testDownwardAPI(f, podName, env, expectations)
	})

	ginkgo.It("should provide host IP and pod IP as an env var if pod uses host network [LinuxOnly]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "HOST_IP",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "status.hostIP",
					},
				},
			},
			{
				Name: "POD_IP",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "status.podIP",
					},
				},
			},
		}

		expectations := []string{
			fmt.Sprintf("OK"),
		}

		pod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:   podName,
				Labels: map[string]string{"name": podName},
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:    "dapi-container",
						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
						Command: []string{"sh", "-c", `[[ "${HOST_IP:?}" == "${POD_IP:?}" ]] && echo 'OK' || echo "HOST_IP: '${HOST_IP}' != POD_IP: '${POD_IP}'"`},
						Env:     env,
					},
				},
				HostNetwork:   true,
				RestartPolicy: v1.RestartPolicyNever,
			},
		}

		testDownwardAPIUsingPod(f, pod, env, expectations)

	})

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for CPU and memory limits and requests
	   Description: Downward API MUST expose CPU request and Memory request set through environment variables at runtime in the container.
	*/
	framework.ConformanceIt("should provide container's limits.cpu/memory and requests.cpu/memory as env vars [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "CPU_LIMIT",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "limits.cpu",
					},
				},
			},
			{
				Name: "MEMORY_LIMIT",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "limits.memory",
					},
				},
			},
			{
				Name: "CPU_REQUEST",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "requests.cpu",
					},
				},
			},
			{
				Name: "MEMORY_REQUEST",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "requests.memory",
					},
				},
			},
		}
		expectations := []string{
			"CPU_LIMIT=2",
			"MEMORY_LIMIT=67108864",
			"CPU_REQUEST=1",
			"MEMORY_REQUEST=33554432",
		}

		testDownwardAPI(f, podName, env, expectations)
	})

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for default CPU and memory limits and requests
	   Description: Downward API MUST expose CPU request and Memory limits set through environment variables at runtime in the container.
	*/
	framework.ConformanceIt("should provide default limits.cpu/memory from node allocatable [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "CPU_LIMIT",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "limits.cpu",
					},
				},
			},
			{
				Name: "MEMORY_LIMIT",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "limits.memory",
					},
				},
			},
		}
		expectations := []string{
			"CPU_LIMIT=[1-9]",
			"MEMORY_LIMIT=[1-9]",
		}
		pod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:   podName,
				Labels: map[string]string{"name": podName},
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:    "dapi-container",
						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
						Command: []string{"sh", "-c", "env"},
						Env:     env,
					},
				},
				RestartPolicy: v1.RestartPolicyNever,
			},
		}

		testDownwardAPIUsingPod(f, pod, env, expectations)
	})

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for Pod UID
	   Description: Downward API MUST expose Pod UID set through environment variables at runtime in the container.
	*/
	framework.ConformanceIt("should provide pod UID as env vars [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "POD_UID",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "metadata.uid",
					},
				},
			},
		}

		expectations := []string{
			"POD_UID=[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}",
		}

		testDownwardAPI(f, podName, env, expectations)
	})
})

var _ = SIGDescribe("Downward API [Serial] [Disruptive] [NodeFeature:DownwardAPIHugePages]", func() {
	f := framework.NewDefaultFramework("downward-api")
	f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged

	ginkgo.Context("Downward API tests for hugepages", func() {
		ginkgo.BeforeEach(func() {
			e2eskipper.SkipUnlessFeatureGateEnabled(kubefeatures.DownwardAPIHugePages)
		})

		ginkgo.It("should provide container's limits.hugepages-<pagesize> and requests.hugepages-<pagesize> as env vars", func() {
			podName := "downward-api-" + string(uuid.NewUUID())
			env := []v1.EnvVar{
				{
					Name: "HUGEPAGES_LIMIT",
					ValueFrom: &v1.EnvVarSource{
						ResourceFieldRef: &v1.ResourceFieldSelector{
							Resource: "limits.hugepages-2Mi",
						},
					},
				},
				{
					Name: "HUGEPAGES_REQUEST",
					ValueFrom: &v1.EnvVarSource{
						ResourceFieldRef: &v1.ResourceFieldSelector{
							Resource: "requests.hugepages-2Mi",
						},
					},
				},
			}

			// Important: we explicitly request no hugepages so the test can run where none are present.
			expectations := []string{
				fmt.Sprintf("HUGEPAGES_LIMIT=%d", 0),
				fmt.Sprintf("HUGEPAGES_REQUEST=%d", 0),
			}
			pod := &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:   podName,
					Labels: map[string]string{"name": podName},
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:    "dapi-container",
							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
							Command: []string{"sh", "-c", "env"},
							Resources: v1.ResourceRequirements{
								Requests: v1.ResourceList{
									"cpu":           resource.MustParse("10m"),
									"hugepages-2Mi": resource.MustParse("0Mi"),
								},
								Limits: v1.ResourceList{
									"hugepages-2Mi": resource.MustParse("0Mi"),
								},
							},
							Env: env,
						},
					},
					RestartPolicy: v1.RestartPolicyNever,
				},
			}
			testDownwardAPIUsingPod(f, pod, env, expectations)
		})

		ginkgo.It("should provide default limits.hugepages-<pagesize> from node allocatable", func() {
			podName := "downward-api-" + string(uuid.NewUUID())
			env := []v1.EnvVar{
				{
					Name: "HUGEPAGES_LIMIT",
					ValueFrom: &v1.EnvVarSource{
						ResourceFieldRef: &v1.ResourceFieldSelector{
							Resource: "limits.hugepages-2Mi",
						},
					},
				},
			}
			// Important: we allow for 0 so the test passes in environments where no hugepages are allocated.
			expectations := []string{
				"HUGEPAGES_LIMIT=((0)|([1-9][0-9]*))\n",
			}
			pod := &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:   podName,
					Labels: map[string]string{"name": podName},
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:    "dapi-container",
							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
							Command: []string{"sh", "-c", "env"},
							Env:     env,
						},
					},
					RestartPolicy: v1.RestartPolicyNever,
				},
			}

			testDownwardAPIUsingPod(f, pod, env, expectations)
		})
	})

})

func testDownwardAPI(f *framework.Framework, podName string, env []v1.EnvVar, expectations []string) {
	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:   podName,
			Labels: map[string]string{"name": podName},
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:    "dapi-container",
					Image:   imageutils.GetE2EImage(imageutils.BusyBox),
					Command: []string{"sh", "-c", "env"},
					Resources: v1.ResourceRequirements{
						Requests: v1.ResourceList{
							v1.ResourceCPU:    resource.MustParse("250m"),
							v1.ResourceMemory: resource.MustParse("32Mi"),
						},
						Limits: v1.ResourceList{
							v1.ResourceCPU:    resource.MustParse("1250m"),
							v1.ResourceMemory: resource.MustParse("64Mi"),
						},
					},
					Env: env,
				},
			},
			RestartPolicy: v1.RestartPolicyNever,
		},
	}

	testDownwardAPIUsingPod(f, pod, env, expectations)
}

func testDownwardAPIUsingPod(f *framework.Framework, pod *v1.Pod, env []v1.EnvVar, expectations []string) {
	f.TestContainerOutputRegexp("downward api env vars", pod, 0, expectations)
}
<commit_msg>do not skip DownwardAPIHugePages<commit_after>/*
Copyright 2016 The Kubernetes 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 node

import (
	"fmt"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/kubernetes/test/e2e/framework"
	e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
	imageutils "k8s.io/kubernetes/test/utils/image"
	admissionapi "k8s.io/pod-security-admission/api"

	"github.com/onsi/ginkgo"
)

var _ = SIGDescribe("Downward API", func() {
	f := framework.NewDefaultFramework("downward-api")
	f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for name, namespace and ip
	   Description: Downward API MUST expose Pod and Container fields as environment variables. Specify Pod Name, namespace and IP as environment variable in the Pod Spec are visible at runtime in the container.
	*/
	framework.ConformanceIt("should provide pod name, namespace and IP address as env vars [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "POD_NAME",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "metadata.name",
					},
				},
			},
			{
				Name: "POD_NAMESPACE",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "metadata.namespace",
					},
				},
			},
			{
				Name: "POD_IP",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "status.podIP",
					},
				},
			},
		}

		expectations := []string{
			fmt.Sprintf("POD_NAME=%v", podName),
			fmt.Sprintf("POD_NAMESPACE=%v", f.Namespace.Name),
			fmt.Sprintf("POD_IP=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6),
		}

		testDownwardAPI(f, podName, env, expectations)
	})

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for host ip
	   Description: Downward API MUST expose Pod and Container fields as environment variables. Specify host IP as environment variable in the Pod Spec are visible at runtime in the container.
	*/
	framework.ConformanceIt("should provide host IP as an env var [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "HOST_IP",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "status.hostIP",
					},
				},
			},
		}

		expectations := []string{
			fmt.Sprintf("HOST_IP=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6),
		}

		testDownwardAPI(f, podName, env, expectations)
	})

	ginkgo.It("should provide host IP and pod IP as an env var if pod uses host network [LinuxOnly]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "HOST_IP",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "status.hostIP",
					},
				},
			},
			{
				Name: "POD_IP",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "status.podIP",
					},
				},
			},
		}

		expectations := []string{
			fmt.Sprintf("OK"),
		}

		pod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:   podName,
				Labels: map[string]string{"name": podName},
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:    "dapi-container",
						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
						Command: []string{"sh", "-c", `[[ "${HOST_IP:?}" == "${POD_IP:?}" ]] && echo 'OK' || echo "HOST_IP: '${HOST_IP}' != POD_IP: '${POD_IP}'"`},
						Env:     env,
					},
				},
				HostNetwork:   true,
				RestartPolicy: v1.RestartPolicyNever,
			},
		}

		testDownwardAPIUsingPod(f, pod, env, expectations)

	})

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for CPU and memory limits and requests
	   Description: Downward API MUST expose CPU request and Memory request set through environment variables at runtime in the container.
	*/
	framework.ConformanceIt("should provide container's limits.cpu/memory and requests.cpu/memory as env vars [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "CPU_LIMIT",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "limits.cpu",
					},
				},
			},
			{
				Name: "MEMORY_LIMIT",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "limits.memory",
					},
				},
			},
			{
				Name: "CPU_REQUEST",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "requests.cpu",
					},
				},
			},
			{
				Name: "MEMORY_REQUEST",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "requests.memory",
					},
				},
			},
		}
		expectations := []string{
			"CPU_LIMIT=2",
			"MEMORY_LIMIT=67108864",
			"CPU_REQUEST=1",
			"MEMORY_REQUEST=33554432",
		}

		testDownwardAPI(f, podName, env, expectations)
	})

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for default CPU and memory limits and requests
	   Description: Downward API MUST expose CPU request and Memory limits set through environment variables at runtime in the container.
	*/
	framework.ConformanceIt("should provide default limits.cpu/memory from node allocatable [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "CPU_LIMIT",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "limits.cpu",
					},
				},
			},
			{
				Name: "MEMORY_LIMIT",
				ValueFrom: &v1.EnvVarSource{
					ResourceFieldRef: &v1.ResourceFieldSelector{
						Resource: "limits.memory",
					},
				},
			},
		}
		expectations := []string{
			"CPU_LIMIT=[1-9]",
			"MEMORY_LIMIT=[1-9]",
		}
		pod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:   podName,
				Labels: map[string]string{"name": podName},
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:    "dapi-container",
						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
						Command: []string{"sh", "-c", "env"},
						Env:     env,
					},
				},
				RestartPolicy: v1.RestartPolicyNever,
			},
		}

		testDownwardAPIUsingPod(f, pod, env, expectations)
	})

	/*
	   Release: v1.9
	   Testname: DownwardAPI, environment for Pod UID
	   Description: Downward API MUST expose Pod UID set through environment variables at runtime in the container.
	*/
	framework.ConformanceIt("should provide pod UID as env vars [NodeConformance]", func() {
		podName := "downward-api-" + string(uuid.NewUUID())
		env := []v1.EnvVar{
			{
				Name: "POD_UID",
				ValueFrom: &v1.EnvVarSource{
					FieldRef: &v1.ObjectFieldSelector{
						APIVersion: "v1",
						FieldPath:  "metadata.uid",
					},
				},
			},
		}

		expectations := []string{
			"POD_UID=[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}",
		}

		testDownwardAPI(f, podName, env, expectations)
	})
})

var _ = SIGDescribe("Downward API [Serial] [Disruptive] [NodeFeature:DownwardAPIHugePages]", func() {
	f := framework.NewDefaultFramework("downward-api")
	f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged

	ginkgo.Context("Downward API tests for hugepages", func() {
		ginkgo.It("should provide container's limits.hugepages-<pagesize> and requests.hugepages-<pagesize> as env vars", func() {
			podName := "downward-api-" + string(uuid.NewUUID())
			env := []v1.EnvVar{
				{
					Name: "HUGEPAGES_LIMIT",
					ValueFrom: &v1.EnvVarSource{
						ResourceFieldRef: &v1.ResourceFieldSelector{
							Resource: "limits.hugepages-2Mi",
						},
					},
				},
				{
					Name: "HUGEPAGES_REQUEST",
					ValueFrom: &v1.EnvVarSource{
						ResourceFieldRef: &v1.ResourceFieldSelector{
							Resource: "requests.hugepages-2Mi",
						},
					},
				},
			}

			// Important: we explicitly request no hugepages so the test can run where none are present.
			expectations := []string{
				fmt.Sprintf("HUGEPAGES_LIMIT=%d", 0),
				fmt.Sprintf("HUGEPAGES_REQUEST=%d", 0),
			}
			pod := &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:   podName,
					Labels: map[string]string{"name": podName},
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:    "dapi-container",
							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
							Command: []string{"sh", "-c", "env"},
							Resources: v1.ResourceRequirements{
								Requests: v1.ResourceList{
									"cpu":           resource.MustParse("10m"),
									"hugepages-2Mi": resource.MustParse("0Mi"),
								},
								Limits: v1.ResourceList{
									"hugepages-2Mi": resource.MustParse("0Mi"),
								},
							},
							Env: env,
						},
					},
					RestartPolicy: v1.RestartPolicyNever,
				},
			}
			testDownwardAPIUsingPod(f, pod, env, expectations)
		})

		ginkgo.It("should provide default limits.hugepages-<pagesize> from node allocatable", func() {
			podName := "downward-api-" + string(uuid.NewUUID())
			env := []v1.EnvVar{
				{
					Name: "HUGEPAGES_LIMIT",
					ValueFrom: &v1.EnvVarSource{
						ResourceFieldRef: &v1.ResourceFieldSelector{
							Resource: "limits.hugepages-2Mi",
						},
					},
				},
			}
			// Important: we allow for 0 so the test passes in environments where no hugepages are allocated.
			expectations := []string{
				"HUGEPAGES_LIMIT=((0)|([1-9][0-9]*))\n",
			}
			pod := &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:   podName,
					Labels: map[string]string{"name": podName},
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:    "dapi-container",
							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
							Command: []string{"sh", "-c", "env"},
							Env:     env,
						},
					},
					RestartPolicy: v1.RestartPolicyNever,
				},
			}

			testDownwardAPIUsingPod(f, pod, env, expectations)
		})
	})

})

func testDownwardAPI(f *framework.Framework, podName string, env []v1.EnvVar, expectations []string) {
	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:   podName,
			Labels: map[string]string{"name": podName},
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:    "dapi-container",
					Image:   imageutils.GetE2EImage(imageutils.BusyBox),
					Command: []string{"sh", "-c", "env"},
					Resources: v1.ResourceRequirements{
						Requests: v1.ResourceList{
							v1.ResourceCPU:    resource.MustParse("250m"),
							v1.ResourceMemory: resource.MustParse("32Mi"),
						},
						Limits: v1.ResourceList{
							v1.ResourceCPU:    resource.MustParse("1250m"),
							v1.ResourceMemory: resource.MustParse("64Mi"),
						},
					},
					Env: env,
				},
			},
			RestartPolicy: v1.RestartPolicyNever,
		},
	}

	testDownwardAPIUsingPod(f, pod, env, expectations)
}

func testDownwardAPIUsingPod(f *framework.Framework, pod *v1.Pod, env []v1.EnvVar, expectations []string) {
	f.TestContainerOutputRegexp("downward api env vars", pod, 0, expectations)
}
<|endoftext|>


<commit_before>package peer

import (
	"image"
	"image/color"
	"image/draw"
	"log"
	"sync"
	"time"

	"github.com/golang/freetype/truetype"
	"github.com/pankona/gomo-simra/simra/config"
	"golang.org/x/image/font"
	"golang.org/x/image/font/gofont/goregular"
	"golang.org/x/image/math/fixed"
	"golang.org/x/mobile/app"
	"golang.org/x/mobile/asset"
	"golang.org/x/mobile/exp/app/debug"
	"golang.org/x/mobile/exp/f32"
	"golang.org/x/mobile/exp/gl/glutil"
	"golang.org/x/mobile/exp/sprite"
	"golang.org/x/mobile/exp/sprite/clock"
	"golang.org/x/mobile/exp/sprite/glsprite"
	"golang.org/x/mobile/gl"
)

// GLer interface represents interface of GL
type GLer interface {
	// Initialize initializes GLPeer.
	// This function must be called inadvance of using GLPeer
	Initialize(glctx gl.Context)
	// LoadTexture return texture that is loaded by the information of arguments.
	// Loaded texture can assign using AddSprite function.
	LoadTexture(assetName string, rect image.Rectangle) sprite.SubTex
	// MakeTextureByText createst and return texture by speicied text
	// Loaded texture can assign using AddSprite function.
	// TODO: font parameterize
	MakeTextureByText(text string, fontsize float64, fontcolor color.RGBA, rect image.Rectangle) sprite.SubTex
	// Finalize finalizes GLPeer.
	// This is called at termination of application.
	Finalize()
	// Update updates screen.
	// This is called 60 times per 1 sec.
	Update(publishFunc func() app.PublishResult)
	// Reset resets current gl context.
	// All sprites are also cleaned.
	// This is called at changing of scene, and
	// this function is for clean previous scene.
	Reset()
	// NewTexture returns a new Texture instance
	NewTexture(s sprite.SubTex) *Texture
	// ReleaseTexture releases specified texture
	ReleaseTexture(t *Texture)
	// NewNode returns new node
	NewNode(fn arrangerFunc) *sprite.Node
	// AppendChild adds specified node as a child
	AppendChild(n *sprite.Node)
	// RemoveChild removes specified node
	RemoveChild(n *sprite.Node)
	// SetSubTex registers subtexture to specified node
	SetSubTex(n *sprite.Node, subTex *sprite.SubTex)
}

var (
	glPeer    = &GLPeer{}
	startTime = time.Now()
)

// GLPeer represents gl context.
// Singleton.
type GLPeer struct {
	glctx  gl.Context
	images *glutil.Images
	fps    *debug.FPS
	eng    sprite.Engine
	scene  *sprite.Node
	mu     sync.Mutex
}

// GetGLPeer returns a instance of GLPeer.
// Since GLPeer is singleton, it is necessary to
// call this function to get GLPeer instance.
func GetGLPeer() GLer {
	return glPeer
}

// Initialize initializes GLPeer.
// This function must be called inadvance of using GLPeer
func (glpeer *GLPeer) Initialize(glctx gl.Context) {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	glpeer.glctx = glctx

	// transparency of png
	glpeer.glctx.Enable(gl.BLEND)
	glpeer.glctx.BlendEquation(gl.FUNC_ADD)
	glpeer.glctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	glpeer.images = glutil.NewImages(glctx)
	glpeer.fps = debug.NewFPS(glpeer.images)
	glpeer.initEng()

	LogDebug("OUT")
}

func (glpeer *GLPeer) initEng() {
	if glpeer.eng != nil {
		glpeer.eng.Release()
	}
	glpeer.eng = glsprite.Engine(glpeer.images)
	glpeer.scene = &sprite.Node{}
	glpeer.eng.Register(glpeer.scene)
	glpeer.eng.SetTransform(glpeer.scene, f32.Affine{
		{1, 0, 0},
		{0, 1, 0},
	})
}

type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time)

func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) }

// NewNode returns new node
func (glpeer *GLPeer) NewNode(fn arrangerFunc) *sprite.Node {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	n := &sprite.Node{Arranger: fn}
	glpeer.eng.Register(n)
	glpeer.scene.AppendChild(n)
	return n
}

// AppendChild adds specified node as a child
func (glpeer *GLPeer) AppendChild(n *sprite.Node) {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	glpeer.scene.AppendChild(n)
}

// RemoveChild removes specified node
func (glpeer *GLPeer) RemoveChild(n *sprite.Node) {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	glpeer.scene.RemoveChild(n)
}

// LoadTexture return texture that is loaded by the information of arguments.
// Loaded texture can assign using AddSprite function.
func (glpeer *GLPeer) LoadTexture(assetName string, rect image.Rectangle) sprite.SubTex {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()

	a, err := asset.Open(assetName)
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		closeErr := a.Close()
		if closeErr != nil {
			log.Println(closeErr)
		}
	}()

	img, _, err := image.Decode(a)
	if err != nil {
		log.Fatal(err)
	}
	t, err := glpeer.eng.LoadTexture(img)
	if err != nil {
		log.Fatal(err)
	}

	LogDebug("OUT")
	return sprite.SubTex{T: t, R: rect}
}

// MakeTextureByText createst and return texture by speicied text
// Loaded texture can assign using AddSprite function.
// TODO: font parameterize
func (glpeer *GLPeer) MakeTextureByText(text string, fontsize float64, fontcolor color.RGBA, rect image.Rectangle) sprite.SubTex {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()

	dpi := float64(72)
	width := rect.Dx()
	height := rect.Dy()
	img := image.NewRGBA(image.Rect(0, 0, width, height))

	fg, bg := image.NewUniform(fontcolor), image.Transparent
	draw.Draw(img, img.Bounds(), bg, image.Point{}, draw.Src)

	// Draw the text.
	h := font.HintingNone

	gofont, _ := truetype.Parse(goregular.TTF)

	d := &font.Drawer{
		Dst: img,
		Src: fg,
		Face: truetype.NewFace(gofont, &truetype.Options{
			Size:    fontsize,
			DPI:     dpi,
			Hinting: h,
		}),
	}

	textWidth := d.MeasureString(text)

	d.Dot = fixed.Point26_6{
		X: fixed.I(width/2) - textWidth/2,
		Y: fixed.I(int(fontsize * dpi / 72)),
	}
	d.DrawString(text)

	t, err := glpeer.eng.LoadTexture(img)
	if err != nil {
		log.Fatal(err)
	}

	LogDebug("OUT")
	return sprite.SubTex{T: t, R: rect}
}

// Finalize finalizes GLPeer.
// This is called at termination of application.
func (glpeer *GLPeer) Finalize() {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()

	GetSpriteContainer().RemoveSprites()
	glpeer.eng.Release()
	glpeer.fps.Release()
	glpeer.images.Release()
	glpeer.glctx = nil
	LogDebug("OUT")
}

// Update updates screen.
// This is called 60 times per 1 sec.
func (glpeer *GLPeer) Update(publishFunc func() app.PublishResult) {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()

	if glpeer.glctx == nil {
		return
	}
	glpeer.glctx.ClearColor(0, 0, 0, 1) // black background
	glpeer.glctx.Clear(gl.COLOR_BUFFER_BIT)
	now := clock.Time(time.Since(startTime) * 60 / time.Second)

	glpeer.apply()

	glpeer.eng.Render(glpeer.scene, now, screensize.sz)
	if config.DEBUG {
		glpeer.fps.Draw(screensize.sz)
	}

	// app.Publish() calls glctx.Flush, it should be called within this mutex locking.
	publishFunc()
}

// Reset resets current gl context.
// All sprites are also cleaned.
// This is called at changing of scene, and
// this function is for clean previous scene.
func (glpeer *GLPeer) Reset() {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	GetSpriteContainer().RemoveSprites()
	glpeer.initEng()
	LogDebug("OUT")
}

// SetSubTex registers subtexture to specified node
func (glpeer *GLPeer) SetSubTex(n *sprite.Node, subTex *sprite.SubTex) {
	glpeer.eng.SetSubTex(n, *subTex)
}

func (glpeer *GLPeer) apply() {

	snpairs := &spriteContainer.spriteNodePairs

	snpairs.Range(func(k, v interface{}) bool {
		sn := v.(*spriteNodePair)
		if sn.sprite == nil || !sn.inuse {
			return true
		}
		s := sn.sprite

		affine := &f32.Affine{
			{1, 0, 0},
			{0, 1, 0},
		}
		affine.Translate(affine,
			(float32)(s.X)*screensize.scale-(float32)(s.W)/2*screensize.scale+screensize.marginWidth/2,
			(screensize.height-(float32)(s.Y))*screensize.scale-(float32)(s.H)/2*screensize.scale+screensize.marginHeight/2)
		if s.R != 0 {
			affine.Translate(affine,
				0.5*(float32)(s.W)*screensize.scale,
				0.5*(float32)(s.H)*screensize.scale)
			affine.Rotate(affine, s.R)
			affine.Translate(affine,
				-0.5*(float32)(s.W)*screensize.scale,
				-0.5*(float32)(s.H)*screensize.scale)
		}
		affine.Scale(affine,
			(float32)(s.W)*screensize.scale,
			(float32)(s.H)*screensize.scale)
		glpeer.eng.SetTransform(sn.node, *affine)
		return true
	})
}

// Texture represents a texture object that contains subTex
type Texture struct {
	glPeer *GLPeer
	subTex sprite.SubTex
}

// NewTexture returns a new Texture instance
func (glpeer *GLPeer) NewTexture(s sprite.SubTex) *Texture {
	return &Texture{
		glPeer: glpeer,
		subTex: s,
	}
}

// ReleaseTexture releases specified texture
func (glpeer *GLPeer) ReleaseTexture(t *Texture) {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	t.subTex.T.Release()
}
<commit_msg>define startTime as a member of GLPeer rather than package global<commit_after>package peer

import (
	"image"
	"image/color"
	"image/draw"
	"log"
	"sync"
	"time"

	"github.com/golang/freetype/truetype"
	"github.com/pankona/gomo-simra/simra/config"
	"golang.org/x/image/font"
	"golang.org/x/image/font/gofont/goregular"
	"golang.org/x/image/math/fixed"
	"golang.org/x/mobile/app"
	"golang.org/x/mobile/asset"
	"golang.org/x/mobile/exp/app/debug"
	"golang.org/x/mobile/exp/f32"
	"golang.org/x/mobile/exp/gl/glutil"
	"golang.org/x/mobile/exp/sprite"
	"golang.org/x/mobile/exp/sprite/clock"
	"golang.org/x/mobile/exp/sprite/glsprite"
	"golang.org/x/mobile/gl"
)

// GLer interface represents interface of GL
type GLer interface {
	// Initialize initializes GLPeer.
	// This function must be called inadvance of using GLPeer
	Initialize(glctx gl.Context)
	// LoadTexture return texture that is loaded by the information of arguments.
	// Loaded texture can assign using AddSprite function.
	LoadTexture(assetName string, rect image.Rectangle) sprite.SubTex
	// MakeTextureByText createst and return texture by speicied text
	// Loaded texture can assign using AddSprite function.
	// TODO: font parameterize
	MakeTextureByText(text string, fontsize float64, fontcolor color.RGBA, rect image.Rectangle) sprite.SubTex
	// Finalize finalizes GLPeer.
	// This is called at termination of application.
	Finalize()
	// Update updates screen.
	// This is called 60 times per 1 sec.
	Update(publishFunc func() app.PublishResult)
	// Reset resets current gl context.
	// All sprites are also cleaned.
	// This is called at changing of scene, and
	// this function is for clean previous scene.
	Reset()
	// NewTexture returns a new Texture instance
	NewTexture(s sprite.SubTex) *Texture
	// ReleaseTexture releases specified texture
	ReleaseTexture(t *Texture)
	// NewNode returns new node
	NewNode(fn arrangerFunc) *sprite.Node
	// AppendChild adds specified node as a child
	AppendChild(n *sprite.Node)
	// RemoveChild removes specified node
	RemoveChild(n *sprite.Node)
	// SetSubTex registers subtexture to specified node
	SetSubTex(n *sprite.Node, subTex *sprite.SubTex)
}

var glPeer = &GLPeer{}

// GLPeer represents gl context.
// Singleton.
type GLPeer struct {
	glctx     gl.Context
	startTime time.Time
	images    *glutil.Images
	fps       *debug.FPS
	eng       sprite.Engine
	scene     *sprite.Node
	mu        sync.Mutex
}

// GetGLPeer returns a instance of GLPeer.
// Since GLPeer is singleton, it is necessary to
// call this function to get GLPeer instance.
func GetGLPeer() GLer {
	return glPeer
}

// Initialize initializes GLPeer.
// This function must be called inadvance of using GLPeer
func (glpeer *GLPeer) Initialize(glctx gl.Context) {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	glpeer.glctx = glctx
	glpeer.startTime = time.Now()

	// transparency of png
	glpeer.glctx.Enable(gl.BLEND)
	glpeer.glctx.BlendEquation(gl.FUNC_ADD)
	glpeer.glctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	glpeer.images = glutil.NewImages(glctx)
	glpeer.fps = debug.NewFPS(glpeer.images)
	glpeer.initEng()

	LogDebug("OUT")
}

func (glpeer *GLPeer) initEng() {
	if glpeer.eng != nil {
		glpeer.eng.Release()
	}
	glpeer.eng = glsprite.Engine(glpeer.images)
	glpeer.scene = &sprite.Node{}
	glpeer.eng.Register(glpeer.scene)
	glpeer.eng.SetTransform(glpeer.scene, f32.Affine{
		{1, 0, 0},
		{0, 1, 0},
	})
}

type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time)

func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) }

// NewNode returns new node
func (glpeer *GLPeer) NewNode(fn arrangerFunc) *sprite.Node {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	n := &sprite.Node{Arranger: fn}
	glpeer.eng.Register(n)
	glpeer.scene.AppendChild(n)
	return n
}

// AppendChild adds specified node as a child
func (glpeer *GLPeer) AppendChild(n *sprite.Node) {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	glpeer.scene.AppendChild(n)
}

// RemoveChild removes specified node
func (glpeer *GLPeer) RemoveChild(n *sprite.Node) {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	glpeer.scene.RemoveChild(n)
}

// LoadTexture return texture that is loaded by the information of arguments.
// Loaded texture can assign using AddSprite function.
func (glpeer *GLPeer) LoadTexture(assetName string, rect image.Rectangle) sprite.SubTex {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()

	a, err := asset.Open(assetName)
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		closeErr := a.Close()
		if closeErr != nil {
			log.Println(closeErr)
		}
	}()

	img, _, err := image.Decode(a)
	if err != nil {
		log.Fatal(err)
	}
	t, err := glpeer.eng.LoadTexture(img)
	if err != nil {
		log.Fatal(err)
	}

	LogDebug("OUT")
	return sprite.SubTex{T: t, R: rect}
}

// MakeTextureByText createst and return texture by speicied text
// Loaded texture can assign using AddSprite function.
// TODO: font parameterize
func (glpeer *GLPeer) MakeTextureByText(text string, fontsize float64, fontcolor color.RGBA, rect image.Rectangle) sprite.SubTex {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()

	dpi := float64(72)
	width := rect.Dx()
	height := rect.Dy()
	img := image.NewRGBA(image.Rect(0, 0, width, height))

	fg, bg := image.NewUniform(fontcolor), image.Transparent
	draw.Draw(img, img.Bounds(), bg, image.Point{}, draw.Src)

	// Draw the text.
	h := font.HintingNone

	gofont, _ := truetype.Parse(goregular.TTF)

	d := &font.Drawer{
		Dst: img,
		Src: fg,
		Face: truetype.NewFace(gofont, &truetype.Options{
			Size:    fontsize,
			DPI:     dpi,
			Hinting: h,
		}),
	}

	textWidth := d.MeasureString(text)

	d.Dot = fixed.Point26_6{
		X: fixed.I(width/2) - textWidth/2,
		Y: fixed.I(int(fontsize * dpi / 72)),
	}
	d.DrawString(text)

	t, err := glpeer.eng.LoadTexture(img)
	if err != nil {
		log.Fatal(err)
	}

	LogDebug("OUT")
	return sprite.SubTex{T: t, R: rect}
}

// Finalize finalizes GLPeer.
// This is called at termination of application.
func (glpeer *GLPeer) Finalize() {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()

	GetSpriteContainer().RemoveSprites()
	glpeer.eng.Release()
	glpeer.fps.Release()
	glpeer.images.Release()
	glpeer.glctx = nil
	LogDebug("OUT")
}

// Update updates screen.
// This is called 60 times per 1 sec.
func (glpeer *GLPeer) Update(publishFunc func() app.PublishResult) {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()

	if glpeer.glctx == nil {
		return
	}
	glpeer.glctx.ClearColor(0, 0, 0, 1) // black background
	glpeer.glctx.Clear(gl.COLOR_BUFFER_BIT)
	now := clock.Time(time.Since(glpeer.startTime) * 60 / time.Second)

	glpeer.apply()

	glpeer.eng.Render(glpeer.scene, now, screensize.sz)
	if config.DEBUG {
		glpeer.fps.Draw(screensize.sz)
	}

	// app.Publish() calls glctx.Flush, it should be called within this mutex locking.
	publishFunc()
}

// Reset resets current gl context.
// All sprites are also cleaned.
// This is called at changing of scene, and
// this function is for clean previous scene.
func (glpeer *GLPeer) Reset() {
	LogDebug("IN")
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	GetSpriteContainer().RemoveSprites()
	glpeer.initEng()
	LogDebug("OUT")
}

// SetSubTex registers subtexture to specified node
func (glpeer *GLPeer) SetSubTex(n *sprite.Node, subTex *sprite.SubTex) {
	glpeer.eng.SetSubTex(n, *subTex)
}

func (glpeer *GLPeer) apply() {

	snpairs := &spriteContainer.spriteNodePairs

	snpairs.Range(func(k, v interface{}) bool {
		sn := v.(*spriteNodePair)
		if sn.sprite == nil || !sn.inuse {
			return true
		}
		s := sn.sprite

		affine := &f32.Affine{
			{1, 0, 0},
			{0, 1, 0},
		}
		affine.Translate(affine,
			(float32)(s.X)*screensize.scale-(float32)(s.W)/2*screensize.scale+screensize.marginWidth/2,
			(screensize.height-(float32)(s.Y))*screensize.scale-(float32)(s.H)/2*screensize.scale+screensize.marginHeight/2)
		if s.R != 0 {
			affine.Translate(affine,
				0.5*(float32)(s.W)*screensize.scale,
				0.5*(float32)(s.H)*screensize.scale)
			affine.Rotate(affine, s.R)
			affine.Translate(affine,
				-0.5*(float32)(s.W)*screensize.scale,
				-0.5*(float32)(s.H)*screensize.scale)
		}
		affine.Scale(affine,
			(float32)(s.W)*screensize.scale,
			(float32)(s.H)*screensize.scale)
		glpeer.eng.SetTransform(sn.node, *affine)
		return true
	})
}

// Texture represents a texture object that contains subTex
type Texture struct {
	glPeer *GLPeer
	subTex sprite.SubTex
}

// NewTexture returns a new Texture instance
func (glpeer *GLPeer) NewTexture(s sprite.SubTex) *Texture {
	return &Texture{
		glPeer: glpeer,
		subTex: s,
	}
}

// ReleaseTexture releases specified texture
func (glpeer *GLPeer) ReleaseTexture(t *Texture) {
	glpeer.mu.Lock()
	defer glpeer.mu.Unlock()
	t.subTex.T.Release()
}
<|endoftext|>


<commit_before>package data

import (
	"fmt"
	"time"

	"appengine"
	"appengine/datastore"

	"bacaberita/parser"
	"bacaberita/utils"
)

type FeedItem struct {
	Title       string
	Link        string
	Guid        string
	Date        time.Time
	Description string

	MediaUrl    string
	MediaLength int
	MediaType   string

	Feed    Feed
	Created time.Time
	Updated time.Time
	Content string
}

func StoreFeedItems(c appengine.Context, data *parser.Feed, parent *datastore.Key) error {
	for _, item := range data.Items {
		feedItem := new(FeedItem)
		feedItem.UpdateFromParser(item)

		key := feedItem.NewKey(c, parent)

		key, err := datastore.Put(c, key, feedItem)
		if err != nil {
			c.Errorf("Error inserting item: url=%s error=%w", feedItem.Link, err)
			return err
		}
	}

	return nil
}

func (item *FeedItem) UpdateFromParser(data *parser.Item) {
	if data.Title != nil {
		item.Title = *data.Title
	}
	if data.Link != nil {
		item.Link = *data.Link
	}
	if data.Guid != nil {
		item.Guid = *data.Guid
	}
	if data.Date != nil {
		item.Date = *data.Date
	}
	if data.Description != nil {
		item.Description = *data.Description
	}

	if data.Media != nil {
		item.MediaUrl = data.Media.Url
		item.MediaLength = data.Media.Length
		item.MediaType = data.Media.Type
	}
}

func (item *FeedItem) NewKey(c appengine.Context, parent *datastore.Key) *datastore.Key {
	id := fmt.Sprintf("%s-%s", utils.Sha1(parent.StringID()), utils.Sha1(item.Link))

	return datastore.NewKey(c, "FeedItem", id, 0, parent)
}
<commit_msg>Store feed items' feed<commit_after>package data

import (
	"fmt"
	"time"

	"appengine"
	"appengine/datastore"

	"bacaberita/parser"
	"bacaberita/utils"
)

type FeedItem struct {
	Title       string
	Link        string
	Guid        string
	Date        time.Time
	Description string

	MediaUrl    string
	MediaLength int
	MediaType   string

	Feed    *datastore.Key
	Created time.Time
	Updated time.Time
	Content string
}

func StoreFeedItems(c appengine.Context, data *parser.Feed, feed *datastore.Key) error {
	for _, item := range data.Items {
		feedItem := new(FeedItem)
		feedItem.UpdateFromParser(item)
		feedItem.Created = time.Now()
		feedItem.Updated = time.Now()
		feedItem.Feed = feed

		key := feedItem.NewKey(c, feed)

		key, err := datastore.Put(c, key, feedItem)
		if err != nil {
			c.Errorf("Error inserting item: url=%s error=%w", feedItem.Link, err)
			return err
		}
	}

	return nil
}

func (item *FeedItem) UpdateFromParser(data *parser.Item) {
	if data.Title != nil {
		item.Title = *data.Title
	}
	if data.Link != nil {
		item.Link = *data.Link
	}
	if data.Guid != nil {
		item.Guid = *data.Guid
	}
	if data.Date != nil {
		item.Date = *data.Date
	}
	if data.Description != nil {
		item.Description = *data.Description
	}

	if data.Media != nil {
		item.MediaUrl = data.Media.Url
		item.MediaLength = data.Media.Length
		item.MediaType = data.Media.Type
	}
}

func (item *FeedItem) NewKey(c appengine.Context, parent *datastore.Key) *datastore.Key {
	id := fmt.Sprintf("%s-%s", utils.Sha1(parent.StringID()), utils.Sha1(item.Link))

	return datastore.NewKey(c, "FeedItem", id, 0, parent)
}
<|endoftext|>


<commit_before>package main

import (
	"net"

	flag "github.com/spf13/pflag"

	"fmt"

	"net/http"
	_ "net/http/pprof"

	"github.com/prasannavl/go-gluons/appx"
	"github.com/prasannavl/go-gluons/http/app-http/app"
	"github.com/prasannavl/go-gluons/log"
	"github.com/prasannavl/go-gluons/logconfig"
)

func main() {
	var addr string
	var logFile string
	var logDisabled bool
	var verbosity int
	var displayVersion bool

	appx.InitTerm()

	flag.Usage = func() {
		printPackageHeader(false)
		fmt.Printf("Usage: [opts]\n\nOptions:\r\n")
		flag.PrintDefaults()
		fmt.Println()
	}

	flag.BoolVar(&displayVersion, "version", false, "display the version and exit")
	flag.CountVarP(&verbosity, "verbose", "v", "verbosity level")
	flag.StringVarP(&addr, "address", "a", "localhost:8000", "the 'host:port' for the service to listen on")
	flag.StringVar(&logFile, "log", "", "the log file destination")
	flag.BoolVar(&logDisabled, "no-log", false, "disable the logger")

	flag.Parse()

	defer func() {
		if err := recover(); err != nil {
			log.Errorv(err)
		}
	}()

	if displayVersion {
		printPackageHeader(true)
		return
	}

	logInitResult := logconfig.LogInitResult{}
	if !logDisabled {
		logOpts := logconfig.DefaultOptions()
		if logFile != "" {
			logOpts.LogFile = logFile
		}
		logOpts.VerbosityLevel = verbosity
		logconfig.Init(&logOpts, &logInitResult)
	}
	log.Infof("listen-address: %q", addr)
	enableProfiler()
	app.Run(logInitResult.Logger, addr)
}

func printPackageHeader(versionOnly bool) {
	if versionOnly {
		fmt.Printf("%s", app.Version)
	} else {
		fmt.Printf("%s\t%s\r\n", app.Package, app.Version)
	}
}

func enableProfiler() {
	go func() {
		l, err := net.Listen("tcp", "localhost:9090")
		if err != nil {
			log.Errorf("pprof-listener: %v", err)
		}
		log.Infof("pprof endpoint: %s", l.Addr())
		http.Serve(l, nil)
	}()
}
<commit_msg>refactor: main, profiler, and redirector<commit_after>package main

import (
	"net"
	"strconv"

	flag "github.com/spf13/pflag"

	"fmt"

	"net/http"
	_ "net/http/pprof"

	"github.com/prasannavl/go-gluons/appx"
	"github.com/prasannavl/go-gluons/http/app-http/app"
	"github.com/prasannavl/go-gluons/http/utils"
	"github.com/prasannavl/go-gluons/log"
	"github.com/prasannavl/go-gluons/logconfig"
)

type EnvFlags struct {
	Addr           string
	LogFile        string
	LogDisabled    bool
	Verbosity      int
	DisplayVersion bool
	PprofAddr      string
	LogHumanize    bool
	Insecure       bool
	RedirectorAddr string
	UseSelfSigned  bool
}

func initFlags(env *EnvFlags) {
	flag.BoolVar(&env.DisplayVersion, "version", false, "display the version and exit")
	flag.CountVarP(&env.Verbosity, "verbose", "v", "verbosity level")
	flag.StringVarP(&env.Addr, "address", "a", "localhost:8000", "the 'host:port' for the service to listen on")
	flag.StringVar(&env.PprofAddr, "pprof-address", "localhost:9090", "the 'host:port' for pprof")
	flag.StringVar(&env.LogFile, "log", "", "the log file destination")
	flag.BoolVar(&env.LogDisabled, "no-log", false, "disable the logger")
	flag.BoolVarP(&env.LogHumanize, "log-humanize", "h", false, "humanize log messages")
	flag.BoolVar(&env.Insecure, "insecure", false, "disable tls")
	flag.BoolVar(&env.UseSelfSigned, "self-signed", false, "use randomly generated self signed certificate for tls")
	flag.StringVar(&env.RedirectorAddr, "redirector", "", "a redirector address as 'host:port' to enable")

	flag.Usage = func() {
		printPackageHeader(false)
		fmt.Printf("Usage: [opts]\n\nOptions:\r\n")
		flag.PrintDefaults()
		fmt.Println()
	}
}

func initLogging(env *EnvFlags) logconfig.LogInitResult {
	logInitResult := logconfig.LogInitResult{}
	if !env.LogDisabled {
		logOpts := logconfig.DefaultOptions()
		if !env.LogHumanize {
			logOpts.Humanize = logconfig.Humanize.False
		}
		if env.LogFile != "" {
			logOpts.LogFile = env.LogFile
		}
		logOpts.VerbosityLevel = env.Verbosity
		logconfig.Init(&logOpts, &logInitResult)
	}
	return logInitResult
}

func printPackageHeader(versionOnly bool) {
	if versionOnly {
		fmt.Printf("%s", app.Version)
	} else {
		fmt.Printf("%s\t%s\r\n", app.Package, app.Version)
	}
}

func setupProfiler(addr string) {
	if addr == "" {
		return
	}
	go func() {
		l, err := net.Listen("tcp", addr)
		if err != nil {
			log.Errorf("pprof-listener: %v", err)
		}
		log.Infof("pprof endpoint: %s", l.Addr())
		http.Serve(l, nil)
	}()
}

func setupRedirector(redirectAddr string, addr string) {
	if redirectAddr == "" {
		return
	}
	hostAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		panic(err)
	}
	go func() {
		l, err := net.Listen("tcp", redirectAddr)
		if err != nil {
			log.Errorf("redirector-listener: %v", err)
		}
		log.Infof("redirector endpoint: %s", l.Addr())
		port := strconv.Itoa(hostAddr.Port)
		shouldIncludePort := true
		if hostAddr.Port == 443 {
			shouldIncludePort = false
		}
		f := func(w http.ResponseWriter, r *http.Request) {
			finalAddr := "https://" + r.Host
			if shouldIncludePort {
				finalAddr += ":" + port
			}
			finalAddr += r.RequestURI
			utils.Redirect(w, r, finalAddr, http.StatusPermanentRedirect)
		}
		http.Serve(l, http.HandlerFunc(f))
	}()
}

func main() {
	appx.InitTerm()

	env := EnvFlags{}
	initFlags(&env)

	flag.Parse()

	defer func() {
		if err := recover(); err != nil {
			log.Errorv(err)
		}
	}()

	if env.DisplayVersion {
		printPackageHeader(true)
		return
	}

	logInitResult := initLogging(&env)
	log.Infof("listen-address: %s", env.Addr)
	setupProfiler(env.PprofAddr)
	setupRedirector(env.RedirectorAddr, env.Addr)
	app.Run(logInitResult.Logger, env.Addr, env.Insecure, env.UseSelfSigned)
}
<|endoftext|>


<commit_before>package httpcheck

import (
	"errors"
	"gopkg.in/yaml.v1"
	"regexp"
)

// Manifest contains one or more Servers, each having some Scenarios.
type Manifest []Server

func (m *Manifest) SetBaseURL(baseurl string) {
	scheme := regexp.MustCompile("^https?:")
	for i := range *m {
		for j := range (*m)[i].Scenarios {
			for k := range (*m)[i].Scenarios[j].Tests {
				match := scheme.MatchString((*m)[i].Scenarios[j].Tests[k].Url)
				if !match {
					url := baseurl + (*m)[i].Scenarios[j].Tests[k].Url
					(*m)[i].Scenarios[j].Tests[k].Url = url
				}
			}
		}
	}
}

// Test runs tests on all servers in the manifest.
// It returns an error if one or more server has errors, or nil otherwise.
// In case there are multiple errors, the error contains the concatenated messages.
func (m Manifest) Test() error {
	if len(m) == 0 {
		return errors.New("Manifest is empty")
	}

	// Start goroutine for every server.Test() call
	// The result of server.Test() is an error or nil. This is passed through a channel.
	var testresults []chan error
	for _, server := range m {
		c := make(chan error)
		defer close(c)
		testresults = append(testresults, c)
		go func(server Server) {
			c <- server.Test()
		}(server)
	}

	// Read from every channel to collect all errors returned.
	// This blocks until every channel has something to receive so all tests are done.
	errorcount := 0
	errorstr := ""
	for _, c := range testresults {
		err := <-c
		if err != nil {
			if errorcount > 0 {
				errorstr += "\n"
			}
			errorstr += err.Error()
			errorcount++
		}
	}

	if errorcount > 0 {
		return errors.New(errorstr)
	} else {
		return nil
	}
}

func FromYAML(y []byte) (Manifest, error) {
	var manifest Manifest
	err := yaml.Unmarshal(y, &manifest)
	if err != nil {
		return nil, err
	}
	return manifest, nil
}
<commit_msg>manifest.SetBaseURL(): remove a double '/' to make prettier URLs<commit_after>package httpcheck

import (
	"errors"
	"gopkg.in/yaml.v1"
	"regexp"
)

// Manifest contains one or more Servers, each having some Scenarios.
type Manifest []Server

func (m *Manifest) SetBaseURL(baseurl string) {
	scheme := regexp.MustCompile("^https?:")
	for i := range *m {
		for j := range (*m)[i].Scenarios {
			for k := range (*m)[i].Scenarios[j].Tests {
				match := scheme.MatchString((*m)[i].Scenarios[j].Tests[k].Url)
				if !match {
					//	This is a relative URL, prefix it with baseurl.
					//	When glueing the URLs together, remove a double '/' to make prettier URLs.
					//	To have a test that starts with '//', add it to the test URL explicitly.
					var url string
					if baseurl[len(baseurl)-1] == '/' && len((*m)[i].Scenarios[j].Tests[k].Url) > 0 &&
						(*m)[i].Scenarios[j].Tests[k].Url[0] == '/' {
						url = baseurl[0:len(baseurl)-1] + (*m)[i].Scenarios[j].Tests[k].Url
					} else {
						url = baseurl + (*m)[i].Scenarios[j].Tests[k].Url
					}
					(*m)[i].Scenarios[j].Tests[k].Url = url
				}
			}
		}
	}
}

// Test runs tests on all servers in the manifest.
// It returns an error if one or more server has errors, or nil otherwise.
// In case there are multiple errors, the error contains the concatenated messages.
func (m Manifest) Test() error {
	if len(m) == 0 {
		return errors.New("Manifest is empty")
	}

	// Start goroutine for every server.Test() call
	// The result of server.Test() is an error or nil. This is passed through a channel.
	var testresults []chan error
	for _, server := range m {
		c := make(chan error)
		defer close(c)
		testresults = append(testresults, c)
		go func(server Server) {
			c <- server.Test()
		}(server)
	}

	// Read from every channel to collect all errors returned.
	// This blocks until every channel has something to receive so all tests are done.
	errorcount := 0
	errorstr := ""
	for _, c := range testresults {
		err := <-c
		if err != nil {
			if errorcount > 0 {
				errorstr += "\n"
			}
			errorstr += err.Error()
			errorcount++
		}
	}

	if errorcount > 0 {
		return errors.New(errorstr)
	} else {
		return nil
	}
}

func FromYAML(y []byte) (Manifest, error) {
	var manifest Manifest
	err := yaml.Unmarshal(y, &manifest)
	if err != nil {
		return nil, err
	}
	return manifest, nil
}
<|endoftext|>


<commit_before>package build

import (
	"github.com/watermint/toolbox/quality/infra/qt_endtoend"
	"github.com/watermint/toolbox/quality/recipe/qtr_endtoend"
	"testing"
)

func TestLicense_Exec(t *testing.T) {
	if qt_endtoend.IsSkipEndToEndTest(){
		return
	}
	qtr_endtoend.TestRecipe(t, &License{})
}
<commit_msg>#523 : auto publish sequence<commit_after>package build

import (
	"github.com/watermint/toolbox/quality/infra/qt_endtoend"
	"github.com/watermint/toolbox/quality/recipe/qtr_endtoend"
	"testing"
)

func TestLicense_Exec(t *testing.T) {
	if qt_endtoend.IsSkipEndToEndTest() {
		return
	}
	qtr_endtoend.TestRecipe(t, &License{})
}
<|endoftext|>


<commit_before>package questions

import (
	"fmt"
	"math/bits"
	"math/rand"
)

// QuestionOne performs addition without using arithmetic operators
func QuestionOne(x, y int64) int64 {
	/*
		 No arithmetic, so binary operations required
		 addition:
			 0 + 0 = 00
			 0 + 1 = 01
			 1 + 0 = 01
			 1 + 1 = 10
		can split addition into adding the result without carrying one
		with the result of carrying the one, without the addition
		this is adding an XOR of the two numbers with and AND shifted by one bit
		101 + 011 = 110 + 010 = 100 + 100 = 000 + 1000

		Alternatively you can iterate through the bits in increasing order of significance and perform AND, carrying the one to the next bit if required.
	*/

	if y == 0 {
		return x
	}
	addition := x ^ y
	carry := (x & y) << 1
	return QuestionOne(addition, carry)
}

const (
	Spade = iota
	Club
	Diamond
	Heart
)

type Card struct {
	suit  int
	value int
}

func (c Card) Print() string {
	switch c.suit {
	case Spade:
		return fmt.Sprintf("S%d ", c.value)
	case Club:
		return fmt.Sprintf("C%d ", c.value)
	case Diamond:
		return fmt.Sprintf("D%d ", c.value)
	case Heart:
		return fmt.Sprintf("H%d ", c.value)
	default:
		return "nope"
	}
}

// QuestionTwo performs a shuffle of a deck of cards, using a perfect random number generator
func QuestionTwo(deck []*Card, position int) []*Card {
	for i := len(deck) - 1; i > 1; i-- {
		k := random(i)
		temp := deck[k]
		deck[k] = deck[i]
		deck[i] = temp
	}
	return deck
}

func initDeck() []*Card {
	deck := []*Card{}
	for _, suit := range []int{Spade, Club, Diamond, Heart} {
		for i := 1; i < 14; i++ {
			deck = append(deck, &Card{suit: suit, value: i})
		}
	}
	return deck
}

// random returns a random int between the 0 and x
func random(x int) int64 {
	return rand.Int63n(int64(x))
}

// QuestionThree generates a random subset of the provided set
func QuestionThree(set []int, count int) []int {
	subset := make([]int, count)
	copy(subset, set[:count])

	for i := count; i < len(set); i++ {
		random := random(len(set))
		if random < int64(count) {
			subset[random] = set[i]
		}
	}
	return subset
}

// Question Four,
func QuestionFour(list []*IntMod, column int64) int64 {
	/*
		An array contains integers from 0 to N, except one is missing
		the integers cannot be accessed directly, but only by the jth bit of array[i]
		find the missing integer in O(n) time

		the missing number will be revealed based on
	*/
	if column >= bits.UintSize {
		return 0
	}

	zeroes := []*IntMod{}
	ones := []*IntMod{}

	for _, i := range list {
		if i.Get(column) {
			// column bit is 1
			ones = append(ones, i)
		} else {
			zeroes = append(zeroes, i)
		}
	}

	if len(zeroes) > len(ones) {
		return (QuestionFour(ones, column+1) << 1) | 1
	} else {
		return (QuestionFour(zeroes, column+1) << 1) | 0
	}
}

type IntMod struct {
	value int64
}

func (i *IntMod) Get(j int64) bool {
	return bits.OnesCount(uint((1<<j)&i.value)) > 0
}

/*
QuestionFive

Given an array of A and B, build the longest sublist containing an equal
number of letters and numbers
*/
func QuestionFive(list []string) []string {
	/*
		Sub string must be even in length
		brute force by checking all subarrays, with some optimisations to allow for early exit

		complexity:
	*/
	var max []string
	for i := len(list) / 2; i > 0; i-- {
		subLength := i * 2
		if CheckSubLength(list, subLength) != nil {
			return CheckSubLength(list, subLength)
		}
	}
	return max
}

func CheckSubLength(list []string, length int) []string {
	for i := 0; i <= len(list)-length; i++ {
		if CheckEven(list[i : i+length]) {
			return list[i : i+length]
		}
	}
	return nil
}

func CheckEven(list []string) bool {
	aCount := 0
	bCount := 0
	for _, element := range list {
		if element == "A" {
			aCount++
		} else {
			bCount++
		}
	}
	return aCount == bCount
}
<commit_msg>Refactor to O(n)<commit_after>package questions

import (
	"fmt"
	"math/bits"
	"math/rand"
)

// QuestionOne performs addition without using arithmetic operators
func QuestionOne(x, y int64) int64 {
	/*
		 No arithmetic, so binary operations required
		 addition:
			 0 + 0 = 00
			 0 + 1 = 01
			 1 + 0 = 01
			 1 + 1 = 10
		can split addition into adding the result without carrying one
		with the result of carrying the one, without the addition
		this is adding an XOR of the two numbers with and AND shifted by one bit
		101 + 011 = 110 + 010 = 100 + 100 = 000 + 1000

		Alternatively you can iterate through the bits in increasing order of significance and perform AND, carrying the one to the next bit if required.
	*/

	if y == 0 {
		return x
	}
	addition := x ^ y
	carry := (x & y) << 1
	return QuestionOne(addition, carry)
}

const (
	Spade = iota
	Club
	Diamond
	Heart
)

type Card struct {
	suit  int
	value int
}

func (c Card) Print() string {
	switch c.suit {
	case Spade:
		return fmt.Sprintf("S%d ", c.value)
	case Club:
		return fmt.Sprintf("C%d ", c.value)
	case Diamond:
		return fmt.Sprintf("D%d ", c.value)
	case Heart:
		return fmt.Sprintf("H%d ", c.value)
	default:
		return "nope"
	}
}

// QuestionTwo performs a shuffle of a deck of cards, using a perfect random number generator
func QuestionTwo(deck []*Card, position int) []*Card {
	for i := len(deck) - 1; i > 1; i-- {
		k := random(i)
		temp := deck[k]
		deck[k] = deck[i]
		deck[i] = temp
	}
	return deck
}

func initDeck() []*Card {
	deck := []*Card{}
	for _, suit := range []int{Spade, Club, Diamond, Heart} {
		for i := 1; i < 14; i++ {
			deck = append(deck, &Card{suit: suit, value: i})
		}
	}
	return deck
}

// random returns a random int between the 0 and x
func random(x int) int64 {
	return rand.Int63n(int64(x))
}

// QuestionThree generates a random subset of the provided set
func QuestionThree(set []int, count int) []int {
	subset := make([]int, count)
	copy(subset, set[:count])

	for i := count; i < len(set); i++ {
		random := random(len(set))
		if random < int64(count) {
			subset[random] = set[i]
		}
	}
	return subset
}

// Question Four,
func QuestionFour(list []*IntMod, column int64) int64 {
	/*
		An array contains integers from 0 to N, except one is missing
		the integers cannot be accessed directly, but only by the jth bit of array[i]
		find the missing integer in O(n) time

		the missing number will be revealed based on
	*/
	if column >= bits.UintSize {
		return 0
	}

	zeroes := []*IntMod{}
	ones := []*IntMod{}

	for _, i := range list {
		if i.Get(column) {
			// column bit is 1
			ones = append(ones, i)
		} else {
			zeroes = append(zeroes, i)
		}
	}

	if len(zeroes) > len(ones) {
		return (QuestionFour(ones, column+1) << 1) | 1
	} else {
		return (QuestionFour(zeroes, column+1) << 1) | 0
	}
}

type IntMod struct {
	value int64
}

func (i *IntMod) Get(j int64) bool {
	return bits.OnesCount(uint((1<<j)&i.value)) > 0
}

/*
QuestionFive

Given an array of A and B, build the longest sublist containing an equal
number of letters and numbers
*/
func QuestionFive(list []string) []string {
	/*
		Sub string must be even in length
		brute force by checking all subarrays, with some optimisations to allow for early exit

		complexity: N3
	*/
	var subArray []string
	differences := map[int]int{0: -1}
	aCount := 0
	bCount := 0
	for i, element := range list {
		if element == "A" {
			aCount++
		} else {
			bCount++
		}
		difference := aCount - bCount
		marker, ok := differences[difference]
		if !ok {
			differences[difference] = i
		} else {
			subLength := i - marker
			if len(subArray) < subLength {
				subArray = list[marker+1 : i+1]
			}
		}
	}
	return subArray
}

func CheckSubLength(list []string, length int) []string {
	for i := 0; i <= len(list)-length; i++ {
		if CheckEven(list[i : i+length]) {
			return list[i : i+length]
		}
	}
	return nil
}

func CheckEven(list []string) bool {
	aCount := 0
	bCount := 0
	for _, element := range list {
		if element == "A" {
			aCount++
		} else {
			bCount++
		}
	}
	return aCount == bCount
}
<|endoftext|>


<commit_before>package space
<commit_msg>space-age: add missing type in downloaded file (#1301)<commit_after>package space

type Planet string
<|endoftext|>


<commit_before>// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gsession

import (
	"github.com/gogf/gf/container/gmap"
	"time"

	"github.com/gogf/gf/os/gcache"
)

// Manager for sessions.
type Manager struct {
	ttl         time.Duration // TTL for sessions.
	storage     Storage       // Storage interface for session storage.
	sessionData *gcache.Cache // Session data cache for session TTL.
}

// New creates and returns a new session manager.
func New(ttl time.Duration, storage ...Storage) *Manager {
	m := &Manager{
		ttl:         ttl,
		sessionData: gcache.New(),
	}
	if len(storage) > 0 && storage[0] != nil {
		m.storage = storage[0]
	} else {
		m.storage = NewStorageFile()
	}
	return m
}

// New creates or fetches the session for given session id.
// The parameter <sessionId> is optional, it creates a new one if not it's passed
// depending on Storage.New.
func (m *Manager) New(sessionId ...string) *Session {
	var id string
	if len(sessionId) > 0 && sessionId[0] != "" {
		id = sessionId[0]
	}
	return &Session{
		id:      id,
		manager: m,
	}
}

// SetStorage sets the session storage for manager.
func (m *Manager) SetStorage(storage Storage) {
	m.storage = storage
}

// SetTTL the TTL for the session manager.
func (m *Manager) SetTTL(ttl time.Duration) {
	m.ttl = ttl
}

// TTL returns the TTL of the session manager.
func (m *Manager) TTL() time.Duration {
	return m.ttl
}

// UpdateSessionTTL updates the ttl for given session.
func (m *Manager) UpdateSessionTTL(id string, data *gmap.StrAnyMap) {
	m.sessionData.Set(id, data, m.ttl)
}
<commit_msg>improve package gsession<commit_after>// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gsession

import (
	"github.com/gogf/gf/container/gmap"
	"time"

	"github.com/gogf/gf/os/gcache"
)

// Manager for sessions.
type Manager struct {
	ttl         time.Duration // TTL for sessions.
	storage     Storage       // Storage interface for session storage.
	sessionData *gcache.Cache // Session data cache for session TTL.
}

// New creates and returns a new session manager.
func New(ttl time.Duration, storage ...Storage) *Manager {
	m := &Manager{
		ttl:         ttl,
		sessionData: gcache.New(),
	}
	if len(storage) > 0 && storage[0] != nil {
		m.storage = storage[0]
	} else {
		m.storage = NewStorageFile()
	}
	return m
}

// New creates or fetches the session for given session id.
// The parameter <sessionId> is optional, it creates a new one if not it's passed
// depending on Storage.New.
func (m *Manager) New(sessionId ...string) *Session {
	var id string
	if len(sessionId) > 0 && sessionId[0] != "" {
		id = sessionId[0]
	}
	return &Session{
		id:      id,
		manager: m,
	}
}

// SetStorage sets the session storage for manager.
func (m *Manager) SetStorage(storage Storage) {
	m.storage = storage
}

// SetTTL the TTL for the session manager.
func (m *Manager) SetTTL(ttl time.Duration) {
	m.ttl = ttl
}

// TTL returns the TTL of the session manager.
func (m *Manager) TTL() time.Duration {
	return m.ttl
}

// UpdateSessionTTL updates the ttl for given session.
func (m *Manager) UpdateSessionTTL(sessionId string, data *gmap.StrAnyMap) {
	m.sessionData.Set(sessionId, data, m.ttl)
}
<|endoftext|>


<commit_before>/*
 *  Copyright 2016 Adobe Systems Incorporated. All rights reserved.
 *  This file is licensed to you 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 REPRESENTATIONS
 *  OF ANY KIND, either express or implied. See the License for the specific language
 *  governing permissions and limitations under the License.
 */
package secrets

import (
	"bytes"
	"encoding/gob"
	"encoding/hex"
	"io/ioutil"
	"os"

	"github.com/adobe-platform/porter/aws_session"
	"github.com/adobe-platform/porter/conf"
	"github.com/adobe-platform/porter/constants"
	"github.com/adobe-platform/porter/util"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/inconshreveable/log15"
)

func Download(log log15.Logger, region *conf.Region) (secretsPayload Payload, success bool) {

	log.Debug("secrets.Download() BEGIN")
	defer log.Debug("secrets.Download() END")

	symmetricKey, secretsLocation, getSecretsKeySuccess := getSecretsKey(log, region.Name)
	if !getSecretsKeySuccess {
		return
	}

	s3Client := s3.New(aws_session.Get(region.Name))

	getObjectInput := &s3.GetObjectInput{
		Bucket: aws.String(region.S3Bucket),
		Key:    aws.String(secretsLocation),
	}

	getObjectOutput, err := s3Client.GetObject(getObjectInput)
	if err != nil {
		log.Crit("GetObject", "Error", err)
		return
	}
	defer getObjectOutput.Body.Close()

	secretsPayloadBytesEnc, err := ioutil.ReadAll(getObjectOutput.Body)
	if err != nil {
		log.Crit("ioutil.ReadAll", "Error", err)
		return
	}

	secretsPayloadBytes, err := Decrypt(secretsPayloadBytesEnc, symmetricKey)
	if err != nil {
		log.Crit("secrets.Decrypt", "Error", err)
		return
	}

	secretsPayload = Payload{}

	err = gob.NewDecoder(bytes.NewReader(secretsPayloadBytes)).Decode(&secretsPayload)
	if err != nil {
		log.Crit("gob.Decode", "Error", err)
		return
	}

	success = true
	return
}

func getSecretsKey(log log15.Logger, region string) (symmetricKey []byte, secretsPayloadLoc string, success bool) {

	log.Debug("getSecretsKey() BEGIN")
	defer log.Debug("getSecretsKey() END")

	var (
		describeStacksOutput *cloudformation.DescribeStacksOutput
		err                  error
	)

	cfnClient := cloudformation.New(aws_session.Get(region))

	describeStacksInput := &cloudformation.DescribeStacksInput{
		StackName: aws.String(os.Getenv("AWS_STACKID")),
	}

	retryMsg := func(i int) { log.Warn("DescribeStacks retrying", "Count", i) }
	if !util.SuccessRetryer(9, retryMsg, func() bool {
		describeStacksOutput, err = cfnClient.DescribeStacks(describeStacksInput)
		if err != nil {
			log.Error("DescribeStacks", "Error", err)
			return false
		}
		if len(describeStacksOutput.Stacks) == 0 {
			log.Error("len(describeStacksOutput.Stacks == 0)")
			return false
		}
		return true
	}) {
		log.Crit("Failed to DescribeStacks")
		return
	}

	if len(describeStacksOutput.Stacks) != 1 {
		log.Crit("len(describeStacksOutput.Stacks != 1)")
		return
	}

	stack := describeStacksOutput.Stacks[0]
	for _, param := range stack.Parameters {
		switch *param.ParameterKey {
		case constants.ParameterSecretsKey:
			symmetricKey, err = hex.DecodeString(*param.ParameterValue)
			if err != nil {
				log.Crit("hex.DecodeString", "Error", err)
				return
			}
		case constants.ParameterSecretsLoc:
			secretsPayloadLoc = *param.ParameterValue
		}
	}

	if len(symmetricKey) == 0 {
		log.Crit("missing parameter key " + constants.ParameterSecretsKey)
	}

	if len(secretsPayloadLoc) == 0 {
		log.Crit("missing parameter key " + constants.ParameterSecretsLoc)
	}

	success = true
	return
}
<commit_msg>4 min cumulative<commit_after>/*
 *  Copyright 2016 Adobe Systems Incorporated. All rights reserved.
 *  This file is licensed to you 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 REPRESENTATIONS
 *  OF ANY KIND, either express or implied. See the License for the specific language
 *  governing permissions and limitations under the License.
 */
package secrets

import (
	"bytes"
	"encoding/gob"
	"encoding/hex"
	"io/ioutil"
	"os"

	"github.com/adobe-platform/porter/aws_session"
	"github.com/adobe-platform/porter/conf"
	"github.com/adobe-platform/porter/constants"
	"github.com/adobe-platform/porter/util"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/inconshreveable/log15"
)

func Download(log log15.Logger, region *conf.Region) (secretsPayload Payload, success bool) {

	log.Debug("secrets.Download() BEGIN")
	defer log.Debug("secrets.Download() END")

	symmetricKey, secretsLocation, getSecretsKeySuccess := getSecretsKey(log, region.Name)
	if !getSecretsKeySuccess {
		return
	}

	s3Client := s3.New(aws_session.Get(region.Name))

	getObjectInput := &s3.GetObjectInput{
		Bucket: aws.String(region.S3Bucket),
		Key:    aws.String(secretsLocation),
	}

	getObjectOutput, err := s3Client.GetObject(getObjectInput)
	if err != nil {
		log.Crit("GetObject", "Error", err)
		return
	}
	defer getObjectOutput.Body.Close()

	secretsPayloadBytesEnc, err := ioutil.ReadAll(getObjectOutput.Body)
	if err != nil {
		log.Crit("ioutil.ReadAll", "Error", err)
		return
	}

	secretsPayloadBytes, err := Decrypt(secretsPayloadBytesEnc, symmetricKey)
	if err != nil {
		log.Crit("secrets.Decrypt", "Error", err)
		return
	}

	secretsPayload = Payload{}

	err = gob.NewDecoder(bytes.NewReader(secretsPayloadBytes)).Decode(&secretsPayload)
	if err != nil {
		log.Crit("gob.Decode", "Error", err)
		return
	}

	success = true
	return
}

func getSecretsKey(log log15.Logger, region string) (symmetricKey []byte, secretsPayloadLoc string, success bool) {

	log.Debug("getSecretsKey() BEGIN")
	defer log.Debug("getSecretsKey() END")

	var (
		describeStacksOutput *cloudformation.DescribeStacksOutput
		err                  error
	)

	cfnClient := cloudformation.New(aws_session.Get(region))

	describeStacksInput := &cloudformation.DescribeStacksInput{
		StackName: aws.String(os.Getenv("AWS_STACKID")),
	}

	retryMsg := func(i int) { log.Warn("DescribeStacks retrying", "Count", i) }
	if !util.SuccessRetryer(7, retryMsg, func() bool {
		describeStacksOutput, err = cfnClient.DescribeStacks(describeStacksInput)
		if err != nil {
			log.Error("DescribeStacks", "Error", err)
			return false
		}
		if len(describeStacksOutput.Stacks) == 0 {
			log.Error("len(describeStacksOutput.Stacks == 0)")
			return false
		}
		return true
	}) {
		log.Crit("Failed to DescribeStacks")
		return
	}

	if len(describeStacksOutput.Stacks) != 1 {
		log.Crit("len(describeStacksOutput.Stacks != 1)")
		return
	}

	stack := describeStacksOutput.Stacks[0]
	for _, param := range stack.Parameters {
		switch *param.ParameterKey {
		case constants.ParameterSecretsKey:
			symmetricKey, err = hex.DecodeString(*param.ParameterValue)
			if err != nil {
				log.Crit("hex.DecodeString", "Error", err)
				return
			}
		case constants.ParameterSecretsLoc:
			secretsPayloadLoc = *param.ParameterValue
		}
	}

	if len(symmetricKey) == 0 {
		log.Crit("missing parameter key " + constants.ParameterSecretsKey)
	}

	if len(secretsPayloadLoc) == 0 {
		log.Crit("missing parameter key " + constants.ParameterSecretsLoc)
	}

	success = true
	return
}
<|endoftext|>