...

Source file src/github.com/datawire/ambassador/v2/pkg/kubeapply/resource_kubeapply.go

Documentation: github.com/datawire/ambassador/v2/pkg/kubeapply

     1  package kubeapply
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	_path "path"
    12  	"path/filepath"
    13  	"strings"
    14  	"text/template"
    15  
    16  	"github.com/Masterminds/sprig"
    17  	"github.com/pkg/errors"
    18  	"gopkg.in/yaml.v2"
    19  
    20  	"github.com/datawire/ambassador/v2/pkg/k8s"
    21  	"github.com/datawire/dlib/dexec"
    22  )
    23  
    24  var readyChecks = map[string]func(k8s.Resource) bool{
    25  	"": func(_ k8s.Resource) bool { return false },
    26  	"Deployment": func(r k8s.Resource) bool {
    27  		// NOTE - plombardi - (2019-05-20)
    28  		// a zero-sized deployment never gets status.readyReplicas and friends set by kubernetes deployment controller.
    29  		// this effectively short-circuits the wait.
    30  		//
    31  		// in the future it might be worth porting this change to StatefulSets, ReplicaSets and ReplicationControllers
    32  		if r.Spec().GetInt64("replicas") == 0 {
    33  			return true
    34  		}
    35  
    36  		return r.Status().GetInt64("readyReplicas") > 0
    37  	},
    38  	"Service": func(r k8s.Resource) bool {
    39  		return true
    40  	},
    41  	"Pod": func(r k8s.Resource) bool {
    42  		css := r.Status().GetMaps("containerStatuses")
    43  		for _, cs := range css {
    44  			if !k8s.Map(cs).GetBool("ready") {
    45  				return false
    46  			}
    47  		}
    48  		return true
    49  	},
    50  	"Namespace": func(r k8s.Resource) bool {
    51  		return r.Status().GetString("phase") == "Active"
    52  	},
    53  	"ServiceAccount": func(r k8s.Resource) bool {
    54  		_, ok := r["secrets"]
    55  		return ok
    56  	},
    57  	"ClusterRole": func(r k8s.Resource) bool {
    58  		return true
    59  	},
    60  	"ClusterRoleBinding": func(r k8s.Resource) bool {
    61  		return true
    62  	},
    63  	"CustomResourceDefinition": func(r k8s.Resource) bool {
    64  		conditions := r.Status().GetMaps("conditions")
    65  		if len(conditions) == 0 {
    66  			return false
    67  		}
    68  		last := conditions[len(conditions)-1]
    69  		return last["status"] == "True"
    70  	},
    71  }
    72  
    73  // ReadyImplemented returns whether or not this package knows how to
    74  // wait for this resource to be ready.
    75  func ReadyImplemented(r k8s.Resource) bool {
    76  	if r.Empty() {
    77  		return false
    78  	}
    79  	kind := r.Kind()
    80  	_, ok := readyChecks[kind]
    81  	return ok
    82  }
    83  
    84  // Ready returns whether or not this resource is ready; if this
    85  // package does not know how to check whether the resource is ready,
    86  // then it returns true.
    87  func Ready(r k8s.Resource) bool {
    88  	if r.Empty() {
    89  		return false
    90  	}
    91  	kind := r.Kind()
    92  	fn, fnOK := readyChecks[kind]
    93  	if !fnOK {
    94  		return true
    95  	}
    96  	return fn(r)
    97  }
    98  
    99  func isTemplate(input []byte) bool {
   100  	return strings.Contains(string(input), "@TEMPLATE@")
   101  }
   102  
   103  func image(ctx context.Context, dir, dockerfile string) (string, error) {
   104  	iidfile, err := ioutil.TempFile("", "iid")
   105  	if err != nil {
   106  		return "", err
   107  	}
   108  	defer os.Remove(iidfile.Name())
   109  	err = iidfile.Close()
   110  	if err != nil {
   111  		return "", err
   112  	}
   113  
   114  	dockerCtx := filepath.Dir(filepath.Join(dir, dockerfile))
   115  	cmd := dexec.CommandContext(ctx, "docker", "build", "-f", filepath.Base(dockerfile), ".", "--iidfile", iidfile.Name())
   116  	cmd.Dir = dockerCtx
   117  	err = cmd.Run()
   118  	if err != nil {
   119  		return "", err
   120  	}
   121  	content, err := ioutil.ReadFile(iidfile.Name())
   122  	if err != nil {
   123  		return "", err
   124  	}
   125  	iid := strings.Split(strings.TrimSpace(string(content)), ":")[1]
   126  	short := iid[:12]
   127  
   128  	registry := strings.TrimSpace(os.Getenv("DOCKER_REGISTRY"))
   129  	if registry == "" {
   130  		return "", errors.Errorf("please set the DOCKER_REGISTRY environment variable")
   131  	}
   132  	tag := fmt.Sprintf("%s/kubeapply:%s", registry, short)
   133  
   134  	if err := dexec.CommandContext(ctx, "docker", "tag", iid, tag).Run(); err != nil {
   135  		return "", err
   136  	}
   137  
   138  	cmd = dexec.CommandContext(ctx, "docker", "push", tag)
   139  	if err := cmd.Run(); err != nil {
   140  		return "", err
   141  	}
   142  
   143  	return tag, nil
   144  }
   145  
   146  // ExpandResource takes a path to a YAML file, and returns its
   147  // contents, with any kubeapply templating expanded.
   148  func ExpandResource(ctx context.Context, path string) (result []byte, err error) {
   149  	input, err := ioutil.ReadFile(path)
   150  	if err != nil {
   151  		return nil, fmt.Errorf("%s: %v", path, err)
   152  	}
   153  	if isTemplate(input) {
   154  		funcs := sprig.TxtFuncMap()
   155  		usedImage := false
   156  		funcs["image"] = func(dockerfile string) (string, error) {
   157  			usedImage = true
   158  			return image(ctx, filepath.Dir(path), dockerfile)
   159  		}
   160  		tmpl := template.New(filepath.Base(path)).Funcs(funcs)
   161  		_, err := tmpl.Parse(string(input))
   162  		if err != nil {
   163  			return nil, fmt.Errorf("%s: %v", path, err)
   164  		}
   165  
   166  		buf := bytes.NewBuffer(nil)
   167  		err = tmpl.ExecuteTemplate(buf, filepath.Base(path), nil)
   168  		if err != nil {
   169  			return nil, fmt.Errorf("%s: %v", path, err)
   170  		}
   171  
   172  		result = buf.Bytes()
   173  
   174  		if usedImage && os.Getenv("DEV_USE_IMAGEPULLSECRET") != "" {
   175  			dockercfg, err := json.Marshal(map[string]interface{}{
   176  				"auths": map[string]interface{}{
   177  					_path.Dir(os.Getenv("DEV_REGISTRY")): map[string]string{
   178  						"auth": base64.StdEncoding.EncodeToString([]byte(os.Getenv("DOCKER_BUILD_USERNAME") + ":" + os.Getenv("DOCKER_BUILD_PASSWORD"))),
   179  					},
   180  				},
   181  			})
   182  			if err != nil {
   183  				return nil, errors.Wrap(err, "DEV_USE_IMAGEPULLSECRET")
   184  			}
   185  
   186  			secretYaml := fmt.Sprintf(`
   187  
   188  ---
   189  apiVersion: v1
   190  kind: Secret
   191  metadata:
   192    name: dev-image-pull-secret
   193  type: kubernetes.io/dockerconfigjson
   194  data:
   195    ".dockerconfigjson": %q
   196  ---
   197  apiVersion: v1
   198  kind: ServiceAccount
   199  metadata:
   200    name: default
   201  imagePullSecrets:
   202  - name: dev-image-pull-secret
   203  `, base64.StdEncoding.EncodeToString(dockercfg))
   204  
   205  			result = append(result, secretYaml...)
   206  		}
   207  	} else {
   208  		result = input
   209  	}
   210  
   211  	return
   212  }
   213  
   214  // LoadResources is like ExpandResource, but follows it up by actually
   215  // parsing the YAML.
   216  func LoadResources(ctx context.Context, path string) (result []k8s.Resource, err error) {
   217  	var input []byte
   218  	input, err = ExpandResource(ctx, path)
   219  	if err != nil {
   220  		return
   221  	}
   222  	result, err = k8s.ParseResources(path, string(input))
   223  	return
   224  }
   225  
   226  // SaveResources serializes a list of k8s.Resources to a YAML file.
   227  func SaveResources(path string, resources []k8s.Resource) error {
   228  	output, err := MarshalResources(resources)
   229  	if err != nil {
   230  		return fmt.Errorf("%s: %v", path, err)
   231  	}
   232  	err = ioutil.WriteFile(path, output, 0644)
   233  	if err != nil {
   234  		return fmt.Errorf("%s: %v", path, err)
   235  	}
   236  	return nil
   237  }
   238  
   239  // MarshalResources serializes a list of k8s.Resources in to YAML.
   240  func MarshalResources(resources []k8s.Resource) ([]byte, error) {
   241  	buf := bytes.NewBuffer(nil)
   242  	e := yaml.NewEncoder(buf)
   243  	for _, r := range resources {
   244  		err := e.Encode(r)
   245  		if err != nil {
   246  			return nil, err
   247  		}
   248  	}
   249  	e.Close()
   250  	return buf.Bytes(), nil
   251  }
   252  

View as plain text