diff --git a/Makefile b/Makefile index de5517984..ae2b9aa07 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ realclean: clean rm -rf ./cache test: - go test ${TEST_FLAGS} $(shell go list ./... | grep -v "^github.com/weaveworks/flux/vendor" | sort -u) + PATH=${PATH}:${PWD}/bin go test ${TEST_FLAGS} $(shell go list ./... | grep -v "^github.com/weaveworks/flux/vendor" | sort -u) build/.%.done: docker/Dockerfile.% mkdir -p ./build/docker/$* diff --git a/bin/kubeyaml b/bin/kubeyaml new file mode 100755 index 000000000..4d6e0128c --- /dev/null +++ b/bin/kubeyaml @@ -0,0 +1,2 @@ +#!/bin/sh +docker run --rm -i squaremo/kubeyaml:latest "$@" diff --git a/cluster/kubernetes/kubeyaml.go b/cluster/kubernetes/kubeyaml.go new file mode 100644 index 000000000..8be162d31 --- /dev/null +++ b/cluster/kubernetes/kubeyaml.go @@ -0,0 +1,42 @@ +package kubernetes + +import ( + "bytes" + "errors" + "os/exec" + "strings" +) + +// KubeYAML is a placeholder value for calling the helper executable +// `kubeyaml`. +type KubeYAML struct { +} + +// Image calls the kubeyaml subcommand `image` with the arguments given. +func (k KubeYAML) Image(in []byte, ns, kind, name, container, image string) ([]byte, error) { + args := []string{"image", "--namespace", ns, "--kind", kind, "--name", name} + args = append(args, "--container", container, "--image", image) + return execKubeyaml(in, args) +} + +// Annotate calls the kubeyaml subcommand `annotate` with the arguments as given. +func (k KubeYAML) Annotate(in []byte, ns, kind, name string, policies ...string) ([]byte, error) { + args := []string{"annotate", "--namespace", ns, "--kind", kind, "--name", name} + args = append(args, policies...) + return execKubeyaml(in, args) +} + +func execKubeyaml(in []byte, args []string) ([]byte, error) { + cmd := exec.Command("kubeyaml", args...) + out := &bytes.Buffer{} + errOut := &bytes.Buffer{} + cmd.Stdin = bytes.NewBuffer(in) + cmd.Stdout = out + cmd.Stderr = errOut + + err := cmd.Run() + if err != nil { + return nil, errors.New(strings.TrimSpace(errOut.String())) + } + return out.Bytes(), nil +} diff --git a/cluster/kubernetes/policies.go b/cluster/kubernetes/policies.go index ac5e58938..df061b582 100644 --- a/cluster/kubernetes/policies.go +++ b/cluster/kubernetes/policies.go @@ -1,8 +1,7 @@ package kubernetes import ( - "regexp" - "strings" + "fmt" "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" @@ -14,6 +13,7 @@ import ( ) func (m *Manifests) UpdatePolicies(def []byte, id flux.ResourceID, update policy.Update) ([]byte, error) { + ns, kind, name := id.Components() add, del := update.Add, update.Remove // We may be sent the pseudo-policy `policy.TagAll`, which means @@ -35,78 +35,15 @@ func (m *Manifests) UpdatePolicies(def []byte, id flux.ResourceID, update policy } } - return updateAnnotations(def, id, func(old map[string]string) { - for k, v := range add { - old[kresource.PolicyPrefix+string(k)] = v - } - for k := range del { - delete(old, kresource.PolicyPrefix+string(k)) - } - }) -} - -func updateAnnotations(def []byte, id flux.ResourceID, f func(map[string]string)) ([]byte, error) { - annotations, err := extractAnnotations(def) - if err != nil { - return nil, err - } - f(annotations) - - // Write the new annotations back into the manifest - // Generate a fragment of the new annotations. - var fragment string - if len(annotations) > 0 { - fragmentB, err := yaml.Marshal(map[string]map[string]string{ - "annotations": annotations, - }) - if err != nil { - return nil, err - } - - fragment = string(fragmentB) - - // Remove the last newline, so it fits in better - fragment = strings.TrimSuffix(fragment, "\n") - - // indent the fragment 2 spaces - // TODO: delete all regular expressions which are used to modify YAML. - // See #1019. Modifying this is not recommended. - fragment = regexp.MustCompile(`(.+)`).ReplaceAllString(fragment, " $1") - - // Add a newline if it's not blank - if len(fragment) > 0 { - fragment = "\n" + fragment - } - } - - // Find where to insert the fragment. - // TODO: delete all regular expressions which are used to modify YAML. - // See #1019. Modifying this is not recommended. - replaced := false - annotationsRE := regexp.MustCompile(`(?m:\n annotations:\s*(?:#.*)*(?:\n .*|\n)*$)`) - newDef := annotationsRE.ReplaceAllStringFunc(string(def), func(found string) string { - if !replaced { - replaced = true - return fragment - } - return found - }) - if !replaced { - metadataRE := multilineRE(`(metadata:\s*(?:#.*)*)`) - newDef = metadataRE.ReplaceAllStringFunc(string(def), func(found string) string { - if !replaced { - replaced = true - f := found + fragment - return f - } - return found - }) + args := []string{} + for pol, val := range add { + args = append(args, fmt.Sprintf("%s%s=%s", kresource.PolicyPrefix, pol, val)) } - if !replaced { - return nil, errors.New("Could not update resource annotations") + for pol, _ := range del { + args = append(args, fmt.Sprintf("%s%s=", kresource.PolicyPrefix, pol)) } - return []byte(newDef), err + return (KubeYAML{}).Annotate(def, ns, kind, name, args...) } type manifest struct { diff --git a/cluster/kubernetes/policies_test.go b/cluster/kubernetes/policies_test.go index 61cdc24f6..8586eca58 100644 --- a/cluster/kubernetes/policies_test.go +++ b/cluster/kubernetes/policies_test.go @@ -2,8 +2,6 @@ package kubernetes import ( "bytes" - "fmt" - "strings" "testing" "text/template" @@ -14,29 +12,29 @@ import ( func TestUpdatePolicies(t *testing.T) { for _, c := range []struct { name string - in, out map[string]string + in, out []string update policy.Update }{ { name: "adding annotation with others existing", - in: map[string]string{"prometheus.io.scrape": "false"}, - out: map[string]string{"flux.weave.works/automated": "true", "prometheus.io.scrape": "false"}, + in: []string{"prometheus.io.scrape", "'false'"}, + out: []string{"prometheus.io.scrape", "'false'", "flux.weave.works/automated", "'true'"}, update: policy.Update{ Add: policy.Set{policy.Automated: "true"}, }, }, { name: "adding annotation when already has annotation", - in: map[string]string{"flux.weave.works/automated": "true"}, - out: map[string]string{"flux.weave.works/automated": "true"}, + in: []string{"flux.weave.works/automated", "'true'"}, + out: []string{"flux.weave.works/automated", "'true'"}, update: policy.Update{ Add: policy.Set{policy.Automated: "true"}, }, }, { name: "adding annotation when already has annotation and others", - in: map[string]string{"flux.weave.works/automated": "true", "prometheus.io.scrape": "false"}, - out: map[string]string{"flux.weave.works/automated": "true", "prometheus.io.scrape": "false"}, + in: []string{"flux.weave.works/automated", "'true'", "prometheus.io.scrape", "'false'"}, + out: []string{"flux.weave.works/automated", "'true'", "prometheus.io.scrape", "'false'"}, update: policy.Update{ Add: policy.Set{policy.Automated: "true"}, }, @@ -44,15 +42,15 @@ func TestUpdatePolicies(t *testing.T) { { name: "adding first annotation", in: nil, - out: map[string]string{"flux.weave.works/automated": "true"}, + out: []string{"flux.weave.works/automated", "'true'"}, update: policy.Update{ Add: policy.Set{policy.Automated: "true"}, }, }, { name: "add and remove different annotations at the same time", - in: map[string]string{"flux.weave.works/automated": "true", "prometheus.io.scrape": "false"}, - out: map[string]string{"flux.weave.works/locked": "true", "prometheus.io.scrape": "false"}, + in: []string{"flux.weave.works/automated", "'true'", "prometheus.io.scrape", "'false'"}, + out: []string{"prometheus.io.scrape", "'false'", "flux.weave.works/locked", "'true'"}, update: policy.Update{ Add: policy.Set{policy.Locked: "true"}, Remove: policy.Set{policy.Automated: "true"}, @@ -69,15 +67,15 @@ func TestUpdatePolicies(t *testing.T) { }, { name: "remove annotation with others existing", - in: map[string]string{"flux.weave.works/automated": "true", "prometheus.io.scrape": "false"}, - out: map[string]string{"prometheus.io.scrape": "false"}, + in: []string{"flux.weave.works/automated", "true", "prometheus.io.scrape", "false"}, + out: []string{"prometheus.io.scrape", "false"}, update: policy.Update{ Remove: policy.Set{policy.Automated: "true"}, }, }, { name: "remove last annotation", - in: map[string]string{"flux.weave.works/automated": "true"}, + in: []string{"flux.weave.works/automated", "true"}, out: nil, update: policy.Update{ Remove: policy.Set{policy.Automated: "true"}, @@ -93,15 +91,15 @@ func TestUpdatePolicies(t *testing.T) { }, { name: "remove annotation with only others", - in: map[string]string{"prometheus.io.scrape": "false"}, - out: map[string]string{"prometheus.io.scrape": "false"}, + in: []string{"prometheus.io.scrape", "false"}, + out: []string{"prometheus.io.scrape", "false"}, update: policy.Update{ Remove: policy.Set{policy.Automated: "true"}, }, }, { name: "multiline", - in: map[string]string{"flux.weave.works/locked_msg": "|-\n first\n second"}, + in: []string{"flux.weave.works/locked_msg", "|-\n first\n second"}, out: nil, update: policy.Update{ Remove: policy.Set{policy.LockedMsg: "foo"}, @@ -109,7 +107,7 @@ func TestUpdatePolicies(t *testing.T) { }, { name: "multiline with empty line", - in: map[string]string{"flux.weave.works/locked_msg": "|-\n first\n\n third"}, + in: []string{"flux.weave.works/locked_msg", "|-\n first\n\n third"}, out: nil, update: policy.Update{ Remove: policy.Set{policy.LockedMsg: "foo"}, @@ -118,8 +116,8 @@ func TestUpdatePolicies(t *testing.T) { } { caseIn := templToString(t, annotationsTemplate, c.in) caseOut := templToString(t, annotationsTemplate, c.out) - id := flux.MustParseResourceID("default:deplot/nginx") - out, err := (&Manifests{}).UpdatePolicies([]byte(caseIn), id, c.update) + resourceID := flux.MustParseResourceID("default:deployment/nginx") + out, err := (&Manifests{}).UpdatePolicies([]byte(caseIn), resourceID, c.update) if err != nil { t.Errorf("[%s] %v", c.name, err) } else if string(out) != caseOut { @@ -132,9 +130,9 @@ var annotationsTemplate = template.Must(template.New("").Parse(`--- apiVersion: extensions/v1beta1 kind: Deployment metadata: # comment really close to the war zone - {{with .}}annotations:{{range $k, $v := .}} - {{$k}}: {{printf "%s" $v}}{{end}} - {{end}}name: nginx + name: nginx{{with .}} + annotations:{{range .}} + {{index . 0}}: {{printf "%s" (index . 1)}}{{end}}{{end}} spec: replicas: 1 template: @@ -149,15 +147,13 @@ spec: - containerPort: 80 `)) -func templToString(t *testing.T, templ *template.Template, annotations map[string]string) string { - for k, v := range annotations { - // Don't wrap multilines - if !strings.HasPrefix(v, "|") { - annotations[k] = fmt.Sprintf("%q", v) - } +func templToString(t *testing.T, templ *template.Template, data []string) string { + var pairs [][]string + for i := 0; i < len(data); i += 2 { + pairs = append(pairs, []string{data[i], data[i+1]}) } out := &bytes.Buffer{} - err := templ.Execute(out, annotations) + err := templ.Execute(out, pairs) if err != nil { t.Fatal(err) } diff --git a/cluster/kubernetes/resource/list.go b/cluster/kubernetes/resource/list.go new file mode 100644 index 000000000..f9f9fc743 --- /dev/null +++ b/cluster/kubernetes/resource/list.go @@ -0,0 +1,10 @@ +package resource + +import ( + "github.com/weaveworks/flux/resource" +) + +type List struct { + baseObject + Items []resource.Resource +} diff --git a/cluster/kubernetes/resource/load.go b/cluster/kubernetes/resource/load.go index 46d8c0476..e1f2b1c02 100644 --- a/cluster/kubernetes/resource/load.go +++ b/cluster/kubernetes/resource/load.go @@ -146,7 +146,15 @@ func ParseMultidoc(multidoc []byte, source string) (map[string]resource.Resource if obj == nil { continue } - objs[obj.ResourceID().String()] = obj + // Lists must be treated specially, since it's the + // contained resources we are after. + if list, ok := obj.(*List); ok { + for _, item := range list.Items { + objs[item.ResourceID().String()] = item + } + } else { + objs[obj.ResourceID().String()] = obj + } } if err := chunks.Err(); err != nil { diff --git a/cluster/kubernetes/resource/load_test.go b/cluster/kubernetes/resource/load_test.go index dfa916ea9..23d8bf53d 100644 --- a/cluster/kubernetes/resource/load_test.go +++ b/cluster/kubernetes/resource/load_test.go @@ -6,6 +6,7 @@ import ( "reflect" "testing" + "github.com/weaveworks/flux" "github.com/weaveworks/flux/cluster/kubernetes/testfiles" "github.com/weaveworks/flux/resource" ) @@ -118,6 +119,39 @@ data: } } +func TestUnmarshalList(t *testing.T) { + doc := `--- +kind: List +metadata: + name: list +items: +- kind: Deployment + metadata: + name: foo +- kind: Service + metadata: + name: bar +` + res, err := unmarshalObject("", []byte(doc)) + if err != nil { + t.Fatal(err) + } + list, ok := res.(*List) + if !ok { + t.Fatal("did not parse as a list") + } + if len(list.Items) != 2 { + t.Fatalf("expected two items, got %+v", list.Items) + } + for i, id := range []flux.ResourceID{ + flux.MustParseResourceID("default:deployment/foo"), + flux.MustParseResourceID("default:service/bar")} { + if list.Items[i].ResourceID() != id { + t.Errorf("At %d, expected %q, got %q", i, id, list.Items[i].ResourceID()) + } + } +} + func debyte(r resource.Resource) resource.Resource { if res, ok := r.(interface { debyte() diff --git a/cluster/kubernetes/resource/resource.go b/cluster/kubernetes/resource/resource.go index a114b3e33..3b766cf5d 100644 --- a/cluster/kubernetes/resource/resource.go +++ b/cluster/kubernetes/resource/resource.go @@ -110,6 +110,14 @@ func unmarshalKind(base baseObject, bytes []byte) (resource.Resource, error) { return nil, err } return &ss, nil + case "List": + var raw rawList + if err := yaml.Unmarshal(bytes, &raw); err != nil { + return nil, err + } + var list List + unmarshalList(base, &raw, &list) + return &list, nil case "": // If there is an empty resource (due to eg an introduced comment), // we are returning nil for the resource and nil for an error @@ -124,6 +132,27 @@ func unmarshalKind(base baseObject, bytes []byte) (resource.Resource, error) { } } +type rawList struct { + Items []map[string]interface{} +} + +func unmarshalList(base baseObject, raw *rawList, list *List) error { + list.baseObject = base + list.Items = make([]resource.Resource, len(raw.Items), len(raw.Items)) + for i, item := range raw.Items { + bytes, err := yaml.Marshal(item) + if err != nil { + return err + } + res, err := unmarshalObject(base.source, bytes) + if err != nil { + return err + } + list.Items[i] = res + } + return nil +} + func makeUnmarshalObjectErr(source string, err error) *fluxerr.Error { return &fluxerr.Error{ Type: fluxerr.User, diff --git a/cluster/kubernetes/update.go b/cluster/kubernetes/update.go index 6ea7bd5de..09da772f6 100644 --- a/cluster/kubernetes/update.go +++ b/cluster/kubernetes/update.go @@ -1,184 +1,21 @@ package kubernetes import ( - "bytes" - "fmt" - "io" - "regexp" "strings" "github.com/weaveworks/flux" "github.com/weaveworks/flux/image" - "github.com/weaveworks/flux/resource" ) -// updatePodController takes the body of a resource definition -// (specified in YAML), the ID of a particular resource and container -// therein, and the name of the new image that should be put in the -// definition (in the format "repo.org/group/name:tag") for that -// resource and container. It returns a new resource definition body -// where all references to the old image have been replaced with the -// new one. -// -// This function has some requirements of the YAML structure. Read the -// source and comments below to learn about them. -func updatePodController(def []byte, resourceID flux.ResourceID, container string, newImageID image.Ref) ([]byte, error) { - // Sanity check - obj, err := parseObj(def) - if err != nil { - return nil, err +// updatePodController takes a YAML document stream (one or more YAML +// docs, as bytes), a resource ID referring to a controller, a +// container name, and the name of the new image that should be used +// for the container. It returns a new YAML stream where the image for +// the container has been replaced with the imageRef supplied. +func updatePodController(in []byte, resource flux.ResourceID, container string, newImageID image.Ref) ([]byte, error) { + namespace, kind, name := resource.Components() + if _, ok := resourceKinds[strings.ToLower(kind)]; !ok { + return nil, UpdateNotSupportedError(kind) } - - if _, ok := resourceKinds[strings.ToLower(obj.Kind)]; !ok { - return nil, UpdateNotSupportedError(obj.Kind) - } - - var buf bytes.Buffer - err = tryUpdate(def, resourceID, container, newImageID, &buf) - return buf.Bytes(), err -} - -// Attempt to update an RC or Deployment config. This makes several assumptions -// that are justified only with the phrase "because that's how we do it", -// including: -// -// * the file is a replication controller or deployment -// * the update is from one tag of an image to another tag of the -// same image; e.g., "weaveworks/helloworld:a00001" to -// "weaveworks/helloworld:a00002" -// * the container spec to update is the (first) one that uses the -// same image name (e.g., weaveworks/helloworld) -// * the name of the controller is updated to reflect the new tag -// * there's a label which must be updated in both the pod spec and the selector -// * the file uses canonical YAML syntax, that is, one line per item -// * ... other assumptions as encoded in the regular expressions used -// -// Here's an example of the assumed structure: -// -// ``` -// apiVersion: v1 -// kind: Deployment # not presently checked -// metadata: # ) -// ... # ) any number of equally-indented lines -// name: helloworld-master-a000001 # ) can precede the name -// spec: -// replicas: 2 -// selector: # ) -// name: helloworld # ) this use of labels is assumed -// version: master-a000001 # ) -// template: -// metadata: -// labels: # ) -// name: helloworld # ) this structure is assumed, as for the selector -// version: master-a000001 # ) -// spec: -// containers: -// # extra container specs are allowed here ... -// - name: helloworld # ) -// image: quay.io/weaveworks/helloworld:master-a000001 # ) these must be together -// args: -// - -msg=Ahoy -// ports: -// - containerPort: 80 -// ``` -func tryUpdate(def []byte, id flux.ResourceID, container string, newImage image.Ref, out io.Writer) error { - containers, err := extractContainers(def, id) - - matchingContainers := map[int]resource.Container{} - for i, c := range containers { - if c.Name != container { - continue - } - currentImage := c.Image - if err != nil { - return fmt.Errorf("could not parse image %s", c.Image) - } - if currentImage.CanonicalName() == newImage.CanonicalName() { - matchingContainers[i] = c - } - } - - if len(matchingContainers) == 0 { - return fmt.Errorf("could not find container using image: %s", newImage.Repository()) - } - - // Detect how indented the "containers" block is. - // TODO: delete all regular expressions which are used to modify YAML. - // See #1019. Modifying this is not recommended. - newDef := string(def) - matches := regexp.MustCompile(`( +)containers:.*`).FindStringSubmatch(newDef) - if len(matches) != 2 { - return fmt.Errorf("could not find container specs") - } - indent := matches[1] - - // TODO: delete all regular expressions which are used to modify YAML. - // See #1019. Modifying this is not recommended. - optq := `["']?` // An optional single or double quote - // Replace the container images - // Parse out all the container blocks - containersRE := regexp.MustCompile(`(?m:^` + indent + `containers:\s*(?:#.*)*$(?:\n(?:` + indent + `[-\s#].*)?)*)`) - // Parse out an individual container blog - containerRE := regexp.MustCompile(`(?m:` + indent + `-.*(?:\n(?:` + indent + `\s+.*)?)*)`) - // Parse out the image ID - imageRE := regexp.MustCompile(`(` + indent + `[-\s]\s*` + optq + `image` + optq + `:\s*)` + optq + `(?:[\w\.\-/:]+\s*?)*` + optq + `([\t\f #]+.*)?`) - imageReplacement := fmt.Sprintf("${1}%s${2}", maybeQuote(newImage.String())) - // Find the block of container specs - newDef = containersRE.ReplaceAllStringFunc(newDef, func(containers string) string { - i := 0 - // Find each container spec - return containerRE.ReplaceAllStringFunc(containers, func(spec string) string { - if _, ok := matchingContainers[i]; ok { - // container matches, let's replace the image - spec = imageRE.ReplaceAllString(spec, imageReplacement) - delete(matchingContainers, i) - } - i++ - return spec - }) - }) - - if len(matchingContainers) > 0 { - missed := []string{} - for _, c := range matchingContainers { - missed = append(missed, c.Name) - } - return fmt.Errorf("did not update expected containers: %s", strings.Join(missed, ", ")) - } - - // TODO: delete all regular expressions which are used to modify YAML. - // See #1019. Modifying this is not recommended. - // Replacing labels: these are in two places, the container template and the selector - // TODO: This doesn't handle # comments - // TODO: This encodes an expectation of map keys being ordered (i.e. name *then* version) - // TODO: This assumes that these are indented by exactly 2 spaces (which may not be true) - replaceLabelsRE := multilineRE( - `((?: selector| labels):.*)`, - `((?: ){2,4}name:.*)`, - `((?: ){2,4}version:\s*) (?:`+optq+`[-\w]+`+optq+`)(\s.*)`, - ) - replaceLabels := fmt.Sprintf("$1\n$2\n$3 %s$4", maybeQuote(newImage.Tag)) - newDef = replaceLabelsRE.ReplaceAllString(newDef, replaceLabels) - - fmt.Fprint(out, newDef) - return nil -} - -func multilineRE(lines ...string) *regexp.Regexp { - return regexp.MustCompile(`(?m:^` + strings.Join(lines, "\n") + `$)`) -} - -// TODO: delete all regular expressions which are used to modify YAML. -// See #1019. Modifying this is not recommended. -var looksLikeNumber *regexp.Regexp = regexp.MustCompile("^(" + strings.Join([]string{ - `(-?[1-9](\.[0-9]*[1-9])?(e[-+][1-9][0-9]*)?)`, - `(-?(0|[1-9][0-9]*))`, - `(0|(\.inf)|(-\.inf)|(\.nan))`}, - "|") + ")$") - -func maybeQuote(scalar string) string { - if looksLikeNumber.MatchString(scalar) { - return `"` + scalar + `"` - } - return scalar + return (KubeYAML{}).Image(in, namespace, kind, name, container, newImageID.String()) } diff --git a/cluster/kubernetes/update_test.go b/cluster/kubernetes/update_test.go index 333cb20a4..6cc0fc2ef 100644 --- a/cluster/kubernetes/update_test.go +++ b/cluster/kubernetes/update_test.go @@ -47,6 +47,8 @@ func TestUpdates(t *testing.T) { {"reordered keys", case6resource, case6containers, case6image, case6, case6out}, {"from prod", case7resource, case7containers, case7image, case7, case7out}, {"single quotes", case8resource, case8containers, case8image, case8, case8out}, + {"in multidoc", case9resource, case9containers, case9image, case9, case9out}, + {"in kubernetes List resource", case10resource, case10containers, case10image, case10, case10out}, } { testUpdate(t, c) } @@ -70,27 +72,27 @@ spec: imagePullSecrets: - name: quay-secret containers: - - name: pr-assigner - image: quay.io/weaveworks/pr-assigner:master-6f5e816 - imagePullPolicy: IfNotPresent - args: - - --conf_path=/config/pr-assigner.json - env: - - name: GITHUB_TOKEN - valueFrom: - secretKeyRef: - name: pr-assigner - key: githubtoken - volumeMounts: - - name: config-volume - mountPath: /config - volumes: + - name: pr-assigner + image: quay.io/weaveworks/pr-assigner:master-6f5e816 + imagePullPolicy: IfNotPresent + args: + - --conf_path=/config/pr-assigner.json + env: + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: pr-assigner + key: githubtoken + volumeMounts: - name: config-volume - configMap: - name: pr-assigner - items: - - key: conffile - path: pr-assigner.json + mountPath: /config + volumes: + - name: config-volume + configMap: + name: pr-assigner + items: + - key: conffile + path: pr-assigner.json ` const case1resource = "extra:deployment/pr-assigner" @@ -114,27 +116,27 @@ spec: imagePullSecrets: - name: quay-secret containers: - - name: pr-assigner - image: quay.io/weaveworks/pr-assigner:master-1234567 - imagePullPolicy: IfNotPresent - args: - - --conf_path=/config/pr-assigner.json - env: - - name: GITHUB_TOKEN - valueFrom: - secretKeyRef: - name: pr-assigner - key: githubtoken - volumeMounts: - - name: config-volume - mountPath: /config - volumes: + - name: pr-assigner + image: quay.io/weaveworks/pr-assigner:master-1234567 + imagePullPolicy: IfNotPresent + args: + - --conf_path=/config/pr-assigner.json + env: + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: pr-assigner + key: githubtoken + volumeMounts: - name: config-volume - configMap: - name: pr-assigner - items: - - key: conffile - path: pr-assigner.json + mountPath: /config + volumes: + - name: config-volume + configMap: + name: pr-assigner + items: + - key: conffile + path: pr-assigner.json ` // Version looks like a number @@ -149,7 +151,6 @@ spec: metadata: labels: name: fluxy - version: master-a000001 spec: volumes: - name: key @@ -194,7 +195,6 @@ spec: metadata: labels: name: fluxy - version: "1234567" spec: volumes: - name: key @@ -230,7 +230,7 @@ apiVersion: extensions/v1beta1 kind: Deployment metadata: namespace: monitoring - name: grafana # comment, and only one space + name: grafana # comment, and only one space indent spec: replicas: 1 template: @@ -262,8 +262,8 @@ const case3out = `--- apiVersion: extensions/v1beta1 kind: Deployment metadata: - namespace: monitoring - name: grafana # comment, and only one space + namespace: monitoring + name: grafana # comment, and only one space indent spec: replicas: 1 template: @@ -313,7 +313,7 @@ spec: runAsUser: 10001 capabilities: drop: - - all + - all readOnlyRootFilesystem: true ` @@ -349,7 +349,7 @@ spec: runAsUser: 10001 capabilities: drop: - - all + - all readOnlyRootFilesystem: true ` @@ -439,7 +439,7 @@ spec: - containerPort: 80 image: nginx:1.10-alpine name: nginx - - image: nginx:1.10-alpine # testing comments, and this image is on the first line. + - image: nginx:1.10-alpine # testing comments, and this image is on the first line. name: nginx2 ` @@ -515,7 +515,7 @@ spec: labels: name: authfe annotations: - prometheus.io.port: "8080" + prometheus.io.port: '8080' spec: # blank comment spacers in the following containers: @@ -578,3 +578,207 @@ spec: - name: weave image: weaveworks/weave-kube:2.2.1 ` + +const case9 = `--- +apiVersion: v1 +kind: Namespace +metadata: + name: hello +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + some.other.com/foo: bar + name: helloworld + namespace: hello +spec: + minReadySeconds: 1 + replicas: 5 + template: + metadata: + labels: + name: helloworld + spec: + containers: + - name: helloworld + image: quay.io/weaveworks/helloworld:master-07a1b6b + args: + - -msg=Bilbo Baggins + ports: + - containerPort: 80 + - name: sidecar + image: quay.io/weaveworks/sidecar:master-a000002 + args: + - -addr=:8080 + ports: + - containerPort: 8080 + restartPolicy: Always +--- +apiVersion: v1 +kind: Service +metadata: + name: helloworld + namespace: hello +spec: + ports: + - port: 80 + selector: + name: helloworld +` + +const case9resource = "hello:deployment/helloworld" +const case9image = "quay.io/weaveworks/helloworld:master-a000001" + +var case9containers = []string{"helloworld"} + +const case9out = `--- +apiVersion: v1 +kind: Namespace +metadata: + name: hello +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + some.other.com/foo: bar + name: helloworld + namespace: hello +spec: + minReadySeconds: 1 + replicas: 5 + template: + metadata: + labels: + name: helloworld + spec: + containers: + - name: helloworld + image: quay.io/weaveworks/helloworld:master-a000001 + args: + - -msg=Bilbo Baggins + ports: + - containerPort: 80 + - name: sidecar + image: quay.io/weaveworks/sidecar:master-a000002 + args: + - -addr=:8080 + ports: + - containerPort: 8080 + restartPolicy: Always +--- +apiVersion: v1 +kind: Service +metadata: + name: helloworld + namespace: hello +spec: + ports: + - port: 80 + selector: + name: helloworld +` + +const case10 = `--- +apiVersion: v1 +kind: List +items: +- apiVersion: v1 + kind: Namespace + metadata: + name: hello +- apiVersion: extensions/v1beta1 + kind: Deployment + metadata: + annotations: + some.other.com/foo: bar + name: helloworld + namespace: hello + spec: + minReadySeconds: 1 + replicas: 5 + template: + metadata: + labels: + name: helloworld + spec: + containers: + - name: helloworld + image: quay.io/weaveworks/helloworld:master-07a1b6b + args: + - -msg=Bilbo Baggins + ports: + - containerPort: 80 + - name: sidecar + image: quay.io/weaveworks/sidecar:master-a000002 + args: + - -addr=:8080 + ports: + - containerPort: 8080 + restartPolicy: Always +- apiVersion: v1 + kind: Service + metadata: + name: helloworld + namespace: hello + spec: + ports: + - port: 80 + selector: + name: helloworld +` + +const case10resource = "hello:deployment/helloworld" +const case10image = "quay.io/weaveworks/helloworld:master-a000001" + +var case10containers = []string{"helloworld"} + +const case10out = `--- +apiVersion: v1 +kind: List +items: +- apiVersion: v1 + kind: Namespace + metadata: + name: hello +- apiVersion: extensions/v1beta1 + kind: Deployment + metadata: + annotations: + some.other.com/foo: bar + name: helloworld + namespace: hello + spec: + minReadySeconds: 1 + replicas: 5 + template: + metadata: + labels: + name: helloworld + spec: + containers: + - name: helloworld + image: quay.io/weaveworks/helloworld:master-a000001 + args: + - -msg=Bilbo Baggins + ports: + - containerPort: 80 + - name: sidecar + image: quay.io/weaveworks/sidecar:master-a000002 + args: + - -addr=:8080 + ports: + - containerPort: 8080 + restartPolicy: Always +- apiVersion: v1 + kind: Service + metadata: + name: helloworld + namespace: hello + spec: + ports: + - port: 80 + selector: + name: helloworld +` diff --git a/docker/Dockerfile.flux b/docker/Dockerfile.flux index 2fa32d0c6..98f449bd4 100644 --- a/docker/Dockerfile.flux +++ b/docker/Dockerfile.flux @@ -18,6 +18,10 @@ WORKDIR /home/flux ENTRYPOINT [ "/sbin/tini", "--", "fluxd" ] RUN apk add --no-cache openssh ca-certificates tini 'git>=2.3.0' +# Get the kubeyaml binary (files) and put them on the path +COPY --from=quay.io/squaremo/kubeyaml:0.2.1 /usr/lib/kubeyaml /usr/lib/kubeyaml/ +ENV PATH=/bin:/usr/bin:/usr/local/bin:/usr/lib/kubeyaml + # Add git hosts to known hosts file so when git ssh's using the deploy # key we don't get an unknown host warning. RUN mkdir ~/.ssh && touch ~/.ssh/known_hosts && \ diff --git a/http/daemon/upstream.go b/http/daemon/upstream.go index 0fb3bb4a6..7e2f0f827 100644 --- a/http/daemon/upstream.go +++ b/http/daemon/upstream.go @@ -175,7 +175,6 @@ func (a *Upstream) setConnectionDuration(duration float64) { } func (a *Upstream) LogEvent(event event.Event) error { - // Instance ID is set via token here, so we can leave it blank. return a.apiClient.LogEvent(context.TODO(), event) } diff --git a/site/requirements.md b/site/requirements.md index 43ed615e0..768247e21 100644 --- a/site/requirements.md +++ b/site/requirements.md @@ -8,18 +8,9 @@ Flux has some requirements of the files it finds in your git repo. * Flux can only deal with one such repo at a time. This limitation is technical and may go away. - * Flux only deals with YAML files at present. It preserves comments - and whitespace in YAMLs when updating them. - - * A controller resource (e.g., Deployment, DaemonSet, CronJob, or - StatefulSet) you want to automate must be in its own file. This is - again a technical limitation, that comes from how the file update - code works, and may at some point be improved upon. - - * Flux doesn't understand Kubernetes List resources. This is yet - again a technical limitation and should get fixed at some point. In - the meantime, you can just put the resources in the list into their - own documents (or files, if you want them automated, as above). + * Flux only deals with YAML files at present. It tries to preserve + comments and whitespace in YAMLs when updating them. You may see + updates with incidental, harmless changes, like reindented blocks. * All Kubernetes resource manifests should explicitly specify the namespace in which you want them to run. Otherwise, the