common.go (262 lines of code) (raw):
// Copyright 2017 Google Inc. 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 daisy
import (
"context"
"math/rand"
"os"
"os/user"
"reflect"
"regexp"
"sort"
"strings"
"time"
computeBeta "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/compute/v1"
)
var (
varPattern = regexp.MustCompile(`\$\{.*\}`)
)
func getUser() string {
if cu, err := user.Current(); err == nil {
return cu.Username
}
if hn, err := os.Hostname(); err == nil {
return hn
}
return "unknown"
}
// NamedSubexp extracts sub matches in the exp
func NamedSubexp(re *regexp.Regexp, s string) map[string]string {
match := re.FindStringSubmatch(s)
if match == nil {
return nil
}
result := make(map[string]string)
l := len(match)
for i, name := range re.SubexpNames() {
if i == 0 || name == "" {
continue
}
result[name] = ""
if i < l {
result[name] = match[i]
}
}
return result
}
// filter creates a copy of ss, excluding any instances of s.
func filter(ss []string, s string) []string {
result := []string{}
for _, element := range ss {
if element != s {
result = append(result, element)
}
}
return result
}
func minInt(x int, ys ...int) int {
for _, y := range ys {
if y < x {
x = y
}
}
return x
}
func randString(n int) string {
gen := rand.New(rand.NewSource(time.Now().UnixNano()))
letters := "bdghjlmnpqrstvwxyz0123456789"
b := make([]byte, n)
for i := range b {
b[i] = letters[gen.Int63()%int64(len(letters))]
}
return string(b)
}
func strIn(s string, ss []string) bool {
for _, x := range ss {
if s == x {
return true
}
}
return false
}
func strLitPtr(s string) *string {
return &s
}
func strOr(s string, ss ...string) string {
ss = append([]string{s}, ss...)
for _, st := range ss {
if st != "" {
return st
}
}
return ""
}
// substitute runs replacer on string elements within a complex data structure
// (except those contained in private data structure fields).
func substitute(v reflect.Value, replacer *strings.Replacer) {
traverseData(v, func(val reflect.Value) DError {
switch val.Interface().(type) {
case string:
val.SetString(replacer.Replace(val.String()))
}
return nil
}, func(v reflect.Value) traverseAction {
_, ok := v.Interface().(*Workflow)
if ok {
return prune
}
return continueTraversal
})
}
// hasVariableDeclaration determines whether s contains a variable declaration of the style `${varname}`
func hasVariableDeclaration(s string) bool {
return varPattern.MatchString(s)
}
func getRegionFromZone(z string) string {
if z != "" {
lastIndex := strings.LastIndex(z, "-")
return z[:lastIndex]
}
return ""
}
// substituteSourceVars replaces source vars (${SOURCE:xxxx}) with the sources
// content.
func (w *Workflow) substituteSourceVars(ctx context.Context, v reflect.Value) DError {
return traverseData(v, func(val reflect.Value) DError {
switch val.Interface().(type) {
case string:
if matches := sourceVarRgx.FindAllStringSubmatch(val.String(), -1); matches != nil {
futureVal := val.String()
for _, match := range matches {
if len(match) < 2 || !w.sourceExists(match[1]) {
return Errf("source not found for expansion: %s", match[0])
}
sv, err := w.sourceContent(ctx, match[1])
if err != nil {
return Errf("error reading source content for %s: %v", match[1], err)
}
futureVal = strings.Replace(futureVal, match[0], sv, -1)
if len(futureVal) > 1024*256 {
return Errf("Expanded string for '%s' is too large", val.String())
}
}
val.SetString(futureVal)
}
}
return nil
})
}
// traverseAction allows callers of traverseData to customize the function's traversal.
type traverseAction uint
const (
// Continue the traversal; this is the default action of
// traverseData, which traverses to all nodes.
continueTraversal traverseAction = iota
// Do not process this node or any of its children.
prune
)
// traverseData traverses complex data structures and runs
// a function, f, on its basic data types.
// Traverses arrays, maps, slices, and public fields of structs.
// For example, f will be run on bool, int, string, etc.
// Slices, maps, and structs will not have f called on them, but will
// traverse their subelements.
// Errors returned from f will be returned by traverseDataStructure.
// actions allows the caller to determine which action to take at a node.
// The default action is 'continueTraverse'.
func traverseData(v reflect.Value, f func(reflect.Value) DError, actions ...func(reflect.Value) traverseAction) DError {
if !v.CanSet() {
// Don't run on private fields.
return nil
}
for _, action := range actions {
switch action(v) {
case prune:
return nil
}
}
switch v.Kind() {
case reflect.Chan, reflect.Func:
return nil
case reflect.Interface, reflect.Ptr, reflect.UnsafePointer:
if v.IsNil() {
return nil
}
// I'm a pointer, dereference me.
return traverseData(v.Elem(), f, actions...)
}
switch v.Kind() {
case reflect.Array, reflect.Slice:
for i := 0; i < v.Len(); i++ {
if err := traverseData(v.Index(i), f, actions...); err != nil {
return err
}
}
case reflect.Map:
kvs := v.MapKeys()
for _, kv := range kvs {
vv := v.MapIndex(kv)
// Create new mutable copies of the key and value.
// Modify the copies.
newKv := reflect.New(kv.Type()).Elem()
newKv.Set(kv)
newVv := reflect.New(vv.Type()).Elem()
newVv.Set(vv)
if err := traverseData(newKv, f, actions...); err != nil {
return err
}
if err := traverseData(newVv, f, actions...); err != nil {
return err
}
// Delete the old key-value.
v.SetMapIndex(kv, reflect.Value{})
// Set the new key-value.
v.SetMapIndex(newKv, newVv)
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if err := traverseData(v.Field(i), f, actions...); err != nil {
return err
}
}
default:
// As far as I can tell, this is a basic data type. Run f on it.
return f(v)
}
return nil
}
func xor(x, y bool) bool {
return x != y
}
// CombineGuestOSFeatures merges two slices of Guest OS features and returns a
// new slice instance. Duplicates are removed.
func CombineGuestOSFeatures(features1 []*compute.GuestOsFeature,
features2 ...string) []*compute.GuestOsFeature {
featureSet := map[string]bool{}
for _, feature := range features2 {
featureSet[feature] = true
}
for _, feature := range features1 {
featureSet[feature.Type] = true
}
ret := make([]*compute.GuestOsFeature, 0)
for feature := range featureSet {
ret = append(ret, &compute.GuestOsFeature{
Type: feature,
})
}
// Sort elements by type, lexically. This ensures
// stability of output ordering for tests.
sort.Slice(ret, func(i, j int) bool {
return ret[i].Type < ret[j].Type
})
return ret
}
// CombineGuestOSFeaturesBeta merges two slices of Beta Guest OS features and
// returns a new slice instance. Duplicates are removed.
func CombineGuestOSFeaturesBeta(features1 []*computeBeta.GuestOsFeature,
features2 ...string) []*computeBeta.GuestOsFeature {
featureSet := map[string]bool{}
for _, feature := range features2 {
featureSet[feature] = true
}
for _, feature := range features1 {
featureSet[feature.Type] = true
}
ret := make([]*computeBeta.GuestOsFeature, 0)
for feature := range featureSet {
ret = append(ret, &computeBeta.GuestOsFeature{
Type: feature,
})
}
// Sort elements by type, lexically. This ensures
// stability of output ordering for tests.
sort.Slice(ret, func(i, j int) bool {
return ret[i].Type < ret[j].Type
})
return ret
}
// UpdateInstanceNoExternalIP updates Create Instance step to operate
// when no external IP access is allowed by the VPC Daisy workflow is running in.
func UpdateInstanceNoExternalIP(step *Step) {
if step.CreateInstances != nil {
for _, instance := range step.CreateInstances.Instances {
if instance.Instance.NetworkInterfaces == nil {
continue
}
for _, networkInterface := range instance.Instance.NetworkInterfaces {
networkInterface.AccessConfigs = []*compute.AccessConfig{}
}
}
for _, instance := range step.CreateInstances.InstancesBeta {
if instance.Instance.NetworkInterfaces == nil {
continue
}
for _, networkInterface := range instance.Instance.NetworkInterfaces {
networkInterface.AccessConfigs = []*computeBeta.AccessConfig{}
}
}
}
}