diff --git a/pkg/app/application.go b/pkg/app/application.go index d16e9be13..77ba7c233 100644 --- a/pkg/app/application.go +++ b/pkg/app/application.go @@ -21,8 +21,11 @@ package app import ( "bytes" "encoding/json" + "fmt" + "strings" "github.com/ghodss/yaml" + "github.com/golang/glog" "github.com/kubernetes-sigs/kustomize/pkg/constants" interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error" @@ -43,6 +46,8 @@ type Application interface { // 1) untransformed resources from current kustomization file // 2) transformed resources from sub packages RawResources() (resmap.ResMap, error) + // Vars returns all the variables defined by the app + Vars() ([]types.Var, error) } var _ Application = &applicationImpl{} @@ -76,7 +81,7 @@ func (a *applicationImpl) Resources() (resmap.ResMap, error) { if err != nil { return nil, err } - t, err := a.getHashAndReferenceTransformer() + t, err := a.newHashAndReferenceTransformer(res) if err != nil { return nil, err } @@ -122,7 +127,7 @@ func (a *applicationImpl) SemiResources() (resmap.ResMap, error) { return nil, errs } - t, err := a.getTransformer(patches) + t, err := a.newTransformer(patches) if err != nil { return nil, err } @@ -130,7 +135,6 @@ func (a *applicationImpl) SemiResources() (resmap.ResMap, error) { if err != nil { return nil, err } - return allRes, nil } @@ -142,7 +146,7 @@ func (a *applicationImpl) RawResources() (resmap.ResMap, error) { if err != nil { return nil, err } - t, err := a.getHashAndReferenceTransformer() + t, err := a.newHashAndReferenceTransformer(res) if err != nil { return nil, err } @@ -196,12 +200,34 @@ func (a *applicationImpl) subAppResources() (resmap.ResMap, *interror.Kustomizat return allResources, errs } -// getTransformer generates the following transformers: +func (a *applicationImpl) subApp() ([]Application, error) { + var apps []Application + errs := &interror.KustomizationErrors{} + for _, basePath := range a.kustomization.Bases { + subloader, err := a.loader.New(basePath) + if err != nil { + errs.Append(err) + continue + } + subapp, err := New(subloader) + if err != nil { + errs.Append(err) + continue + } + apps = append(apps, subapp) + } + if len(errs.Get()) > 0 { + return nil, errs + } + return apps, nil +} + +// newTransformer generates the following transformers: // 1) apply overlay // 2) name prefix // 3) apply labels // 4) apply annotations -func (a *applicationImpl) getTransformer(patches []*resource.Resource) (transformers.Transformer, error) { +func (a *applicationImpl) newTransformer(patches []*resource.Resource) (transformers.Transformer, error) { ts := []transformers.Transformer{} ot, err := transformers.NewOverlayTransformer(patches) @@ -233,10 +259,11 @@ func (a *applicationImpl) getTransformer(patches []*resource.Resource) (transfor return transformers.NewMultiTransformer(ts), nil } -// getHashAndReferenceTransformer generates the following transformers: +// newHashAndReferenceTransformer generates the following transformers: // 1) name hash for configmap and secrests // 2) apply name reference -func (a *applicationImpl) getHashAndReferenceTransformer() (transformers.Transformer, error) { +// 3) apply reference variables +func (a *applicationImpl) newHashAndReferenceTransformer(allRes resmap.ResMap) (transformers.Transformer, error) { ts := []transformers.Transformer{} nht := transformers.NewNameHashTransformer() ts = append(ts, nht) @@ -246,9 +273,30 @@ func (a *applicationImpl) getHashAndReferenceTransformer() (transformers.Transfo return nil, err } ts = append(ts, nrt) + t, err := a.newVariableReferenceTransformer(allRes) + if err != nil { + return nil, err + } + ts = append(ts, t) + return transformers.NewMultiTransformer(ts), nil } +func (a *applicationImpl) newVariableReferenceTransformer(allRes resmap.ResMap) (transformers.Transformer, error) { + refvars, err := a.resolveRefVars(allRes) + if err != nil { + return nil, err + } + + glog.Infof("found all the refvars: %+v", refvars) + + varExpander, err := transformers.NewRefVarTransformer(refvars) + if err != nil { + return nil, err + } + return varExpander, nil +} + func unmarshal(y []byte, o interface{}) error { j, err := yaml.YAMLToJSON(y) if err != nil { @@ -259,3 +307,54 @@ func unmarshal(y []byte, o interface{}) error { dec.DisallowUnknownFields() return dec.Decode(o) } + +func (a *applicationImpl) resolveRefVars(resources resmap.ResMap) (map[string]string, error) { + refvars := map[string]string{} + vars, err := a.Vars() + if err != nil { + return refvars, err + } + + for _, refvar := range vars { + refGVKN := resource.NewResId(refvar.ObjRef.GroupVersionKind(), refvar.ObjRef.Name) + if r, found := resources[refGVKN]; found { + s, err := resource.GetFieldValue(r.Unstruct().UnstructuredContent(), strings.Split(refvar.FieldRef.FieldPath, ".")) + if err != nil { + return nil, fmt.Errorf("failed to resolve referred var: %+v", refvar) + } + refvars[refvar.Name] = s + } else { + glog.Infof("couldn't resolve refvar: %v", refvar) + } + } + return refvars, nil +} + +// Vars returns all the variables defined at the app and subapps of the app +func (a *applicationImpl) Vars() ([]types.Var, error) { + vars := []types.Var{} + errs := &interror.KustomizationErrors{} + + apps, err := a.subApp() + if err != nil { + return nil, err + } + + // TODO: computing vars and resources for subApps can be combined + for _, subApp := range apps { + subAppVars, err := subApp.Vars() + if err != nil { + errs.Append(err) + continue + } + vars = append(vars, subAppVars...) + } + for _, v := range a.kustomization.Vars { + v.Defaulting() + vars = append(vars, v) + } + if len(errs.Get()) > 0 { + return nil, errs + } + return vars, nil +} diff --git a/pkg/commands/testdata/testcase-variable-ref/expected.diff b/pkg/commands/testdata/testcase-variable-ref/expected.diff new file mode 100644 index 000000000..124aca43e --- /dev/null +++ b/pkg/commands/testdata/testcase-variable-ref/expected.diff @@ -0,0 +1,150 @@ +diff -u -N /tmp/noop/apps_v1beta1_StatefulSet_cockroachdb.yaml /tmp/transformed/apps_v1beta1_StatefulSet_cockroachdb.yaml +--- /tmp/noop/apps_v1beta1_StatefulSet_cockroachdb.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/apps_v1beta1_StatefulSet_cockroachdb.yaml YYYY-MM-DD HH:MM:SS +@@ -1,10 +1,10 @@ + apiVersion: apps/v1beta1 + kind: StatefulSet + metadata: +- name: base-cockroachdb ++ name: dev-base-cockroachdb + spec: + replicas: 3 +- serviceName: base-cockroachdb ++ serviceName: dev-base-cockroachdb + template: + metadata: + labels: +@@ -27,7 +27,7 @@ + - /bin/bash + - -ecx + - exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs +- --host $(hostname -f) --http-host 0.0.0.0 --join base-cockroachdb-0.base-cockroachdb,base-cockroachdb-1.base-cockroachdb,base-cockroachdb-2.base-cockroachdb ++ --host $(hostname -f) --http-host 0.0.0.0 --join dev-base-cockroachdb-0.dev-base-cockroachdb,dev-base-cockroachdb-1.dev-base-cockroachdb,dev-base-cockroachdb-2.dev-base-cockroachdb + --cache 25% --max-sql-memory 25% + image: cockroachdb/cockroach:v1.1.5 + imagePullPolicy: IfNotPresent +@@ -48,7 +48,7 @@ + - -ecx + - /request-cert -namespace=${POD_NAMESPACE} -certs-dir=/cockroach-certs -type=node + -addresses=localhost,127.0.0.1,${POD_IP},$(hostname -f),$(hostname -f|cut +- -f 1-2 -d '.'),base-cockroachdb-public -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt ++ -f 1-2 -d '.'),dev-base-cockroachdb-public -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt + env: + - name: POD_IP + valueFrom: +@@ -64,7 +64,7 @@ + volumeMounts: + - mountPath: /cockroach-certs + name: certs +- serviceAccountName: base-cockroachdb ++ serviceAccountName: dev-base-cockroachdb + terminationGracePeriodSeconds: 60 + volumes: + - name: datadir +diff -u -N /tmp/noop/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml /tmp/transformed/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml +--- /tmp/noop/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml YYYY-MM-DD HH:MM:SS +@@ -3,7 +3,7 @@ + metadata: + labels: + app: cockroachdb +- name: base-cockroachdb-budget ++ name: dev-base-cockroachdb-budget + spec: + maxUnavailable: 1 + selector: +diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_ClusterRoleBinding_cockroachdb.yaml /tmp/transformed/rbac.authorization.k8s.io_v1beta1_ClusterRoleBinding_cockroachdb.yaml +--- /tmp/noop/rbac.authorization.k8s.io_v1beta1_ClusterRoleBinding_cockroachdb.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/rbac.authorization.k8s.io_v1beta1_ClusterRoleBinding_cockroachdb.yaml YYYY-MM-DD HH:MM:SS +@@ -3,12 +3,12 @@ + metadata: + labels: + app: cockroachdb +- name: base-cockroachdb ++ name: dev-base-cockroachdb + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole +- name: base-cockroachdb ++ name: dev-base-cockroachdb + subjects: + - kind: ServiceAccount +- name: base-cockroachdb ++ name: dev-base-cockroachdb + namespace: default +diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_ClusterRole_cockroachdb.yaml /tmp/transformed/rbac.authorization.k8s.io_v1beta1_ClusterRole_cockroachdb.yaml +--- /tmp/noop/rbac.authorization.k8s.io_v1beta1_ClusterRole_cockroachdb.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/rbac.authorization.k8s.io_v1beta1_ClusterRole_cockroachdb.yaml YYYY-MM-DD HH:MM:SS +@@ -3,7 +3,7 @@ + metadata: + labels: + app: cockroachdb +- name: base-cockroachdb ++ name: dev-base-cockroachdb + rules: + - apiGroups: + - certificates.k8s.io +diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_RoleBinding_cockroachdb.yaml /tmp/transformed/rbac.authorization.k8s.io_v1beta1_RoleBinding_cockroachdb.yaml +--- /tmp/noop/rbac.authorization.k8s.io_v1beta1_RoleBinding_cockroachdb.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/rbac.authorization.k8s.io_v1beta1_RoleBinding_cockroachdb.yaml YYYY-MM-DD HH:MM:SS +@@ -3,12 +3,12 @@ + metadata: + labels: + app: cockroachdb +- name: base-cockroachdb ++ name: dev-base-cockroachdb + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role +- name: base-cockroachdb ++ name: dev-base-cockroachdb + subjects: + - kind: ServiceAccount +- name: base-cockroachdb ++ name: dev-base-cockroachdb + namespace: default +diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml /tmp/transformed/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml +--- /tmp/noop/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml YYYY-MM-DD HH:MM:SS +@@ -3,7 +3,7 @@ + metadata: + labels: + app: cockroachdb +- name: base-cockroachdb ++ name: dev-base-cockroachdb + rules: + - apiGroups: + - "" +diff -u -N /tmp/noop/v1_ServiceAccount_cockroachdb.yaml /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml +--- /tmp/noop/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS +@@ -3,4 +3,4 @@ + metadata: + labels: + app: cockroachdb +- name: base-cockroachdb ++ name: dev-base-cockroachdb +diff -u -N /tmp/noop/v1_Service_cockroachdb-public.yaml /tmp/transformed/v1_Service_cockroachdb-public.yaml +--- /tmp/noop/v1_Service_cockroachdb-public.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/v1_Service_cockroachdb-public.yaml YYYY-MM-DD HH:MM:SS +@@ -3,7 +3,7 @@ + metadata: + labels: + app: cockroachdb +- name: base-cockroachdb-public ++ name: dev-base-cockroachdb-public + spec: + ports: + - name: grpc +diff -u -N /tmp/noop/v1_Service_cockroachdb.yaml /tmp/transformed/v1_Service_cockroachdb.yaml +--- /tmp/noop/v1_Service_cockroachdb.yaml YYYY-MM-DD HH:MM:SS ++++ /tmp/transformed/v1_Service_cockroachdb.yaml YYYY-MM-DD HH:MM:SS +@@ -8,7 +8,7 @@ + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + labels: + app: cockroachdb +- name: base-cockroachdb ++ name: dev-base-cockroachdb + spec: + clusterIP: None + ports: diff --git a/pkg/commands/testdata/testcase-variable-ref/expected.yaml b/pkg/commands/testdata/testcase-variable-ref/expected.yaml new file mode 100644 index 000000000..74b64270a --- /dev/null +++ b/pkg/commands/testdata/testcase-variable-ref/expected.yaml @@ -0,0 +1,205 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + prometheus.io/path: _status/vars + prometheus.io/port: "8080" + prometheus.io/scrape: "true" + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + labels: + app: cockroachdb + name: dev-base-cockroachdb +spec: + clusterIP: None + ports: + - name: grpc + port: 26257 + targetPort: 26257 + - name: http + port: 8080 + targetPort: 8080 + selector: + app: cockroachdb +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: cockroachdb + name: dev-base-cockroachdb-public +spec: + ports: + - name: grpc + port: 26257 + targetPort: 26257 + - name: http + port: 8080 + targetPort: 8080 + selector: + app: cockroachdb +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: cockroachdb + name: dev-base-cockroachdb +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: dev-base-cockroachdb +spec: + replicas: 3 + serviceName: dev-base-cockroachdb + template: + metadata: + labels: + app: cockroachdb + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - cockroachdb + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - command: + - /bin/bash + - -ecx + - exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs + --host $(hostname -f) --http-host 0.0.0.0 --join dev-base-cockroachdb-0.dev-base-cockroachdb,dev-base-cockroachdb-1.dev-base-cockroachdb,dev-base-cockroachdb-2.dev-base-cockroachdb + --cache 25% --max-sql-memory 25% + image: cockroachdb/cockroach:v1.1.5 + imagePullPolicy: IfNotPresent + name: cockroachdb + ports: + - containerPort: 26257 + name: grpc + - containerPort: 8080 + name: http + volumeMounts: + - mountPath: /cockroach/cockroach-data + name: datadir + - mountPath: /cockroach/cockroach-certs + name: certs + initContainers: + - command: + - /bin/ash + - -ecx + - /request-cert -namespace=${POD_NAMESPACE} -certs-dir=/cockroach-certs -type=node + -addresses=localhost,127.0.0.1,${POD_IP},$(hostname -f),$(hostname -f|cut + -f 1-2 -d '.'),dev-base-cockroachdb-public -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: cockroachdb/cockroach-k8s-request-cert:0.2 + imagePullPolicy: IfNotPresent + name: init-certs + volumeMounts: + - mountPath: /cockroach-certs + name: certs + serviceAccountName: dev-base-cockroachdb + terminationGracePeriodSeconds: 60 + volumes: + - name: datadir + persistentVolumeClaim: + claimName: datadir + - emptyDir: {} + name: certs + updateStrategy: + type: RollingUpdate + volumeClaimTemplates: + - metadata: + name: datadir + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + labels: + app: cockroachdb + name: dev-base-cockroachdb-budget +spec: + maxUnavailable: 1 + selector: + matchLabels: + app: cockroachdb +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + labels: + app: cockroachdb + name: dev-base-cockroachdb +rules: +- apiGroups: + - certificates.k8s.io + resources: + - certificatesigningrequests + verbs: + - create + - get + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + labels: + app: cockroachdb + name: dev-base-cockroachdb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: dev-base-cockroachdb +subjects: +- kind: ServiceAccount + name: dev-base-cockroachdb + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + labels: + app: cockroachdb + name: dev-base-cockroachdb +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - get +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + labels: + app: cockroachdb + name: dev-base-cockroachdb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: dev-base-cockroachdb +subjects: +- kind: ServiceAccount + name: dev-base-cockroachdb + namespace: default diff --git a/pkg/commands/testdata/testcase-variable-ref/in/overlay/kustomization.yaml b/pkg/commands/testdata/testcase-variable-ref/in/overlay/kustomization.yaml new file mode 100644 index 000000000..1fccccac8 --- /dev/null +++ b/pkg/commands/testdata/testcase-variable-ref/in/overlay/kustomization.yaml @@ -0,0 +1,4 @@ +namePrefix: dev- +bases: +- ../package + diff --git a/pkg/commands/testdata/testcase-variable-ref/in/package/cockroachdb-statefulset-secure.yaml b/pkg/commands/testdata/testcase-variable-ref/in/package/cockroachdb-statefulset-secure.yaml new file mode 100644 index 000000000..5feb4a669 --- /dev/null +++ b/pkg/commands/testdata/testcase-variable-ref/in/package/cockroachdb-statefulset-secure.yaml @@ -0,0 +1,235 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cockroachdb + labels: + app: cockroachdb +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: cockroachdb + labels: + app: cockroachdb +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - get +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: cockroachdb + labels: + app: cockroachdb +rules: +- apiGroups: + - certificates.k8s.io + resources: + - certificatesigningrequests + verbs: + - create + - get + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: cockroachdb + labels: + app: cockroachdb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cockroachdb +subjects: +- kind: ServiceAccount + name: cockroachdb + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: cockroachdb + labels: + app: cockroachdb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cockroachdb +subjects: +- kind: ServiceAccount + name: cockroachdb + namespace: default +--- +apiVersion: v1 +kind: Service +metadata: + # This service is meant to be used by clients of the database. It exposes a ClusterIP that will + # automatically load balance connections to the different database pods. + name: cockroachdb-public + labels: + app: cockroachdb +spec: + ports: + # The main port, served by gRPC, serves Postgres-flavor SQL, internode + # traffic and the cli. + - port: 26257 + targetPort: 26257 + name: grpc + # The secondary port serves the UI as well as health and debug endpoints. + - port: 8080 + targetPort: 8080 + name: http + selector: + app: cockroachdb +--- +apiVersion: v1 +kind: Service +metadata: + # This service only exists to create DNS entries for each pod in the stateful + # set such that they can resolve each other's IP addresses. It does not + # create a load-balanced ClusterIP and should not be used directly by clients + # in most circumstances. + name: cockroachdb + labels: + app: cockroachdb + annotations: + # This is needed to make the peer-finder work properly and to help avoid + # edge cases where instance 0 comes up after losing its data and needs to + # decide whether it should create a new cluster or try to join an existing + # one. If it creates a new cluster when it should have joined an existing + # one, we'd end up with two separate clusters listening at the same service + # endpoint, which would be very bad. + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + # Enable automatic monitoring of all instances when Prometheus is running in the cluster. + prometheus.io/scrape: "true" + prometheus.io/path: "_status/vars" + prometheus.io/port: "8080" +spec: + ports: + - port: 26257 + targetPort: 26257 + name: grpc + - port: 8080 + targetPort: 8080 + name: http + clusterIP: None + selector: + app: cockroachdb +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: cockroachdb-budget + labels: + app: cockroachdb +spec: + selector: + matchLabels: + app: cockroachdb + maxUnavailable: 1 +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: cockroachdb +spec: + serviceName: "cockroachdb" + replicas: 3 + template: + metadata: + labels: + app: cockroachdb + spec: + serviceAccountName: cockroachdb + # Init containers are run only once in the lifetime of a pod, before + # it's started up for the first time. It has to exit successfully + # before the pod's main containers are allowed to start. + initContainers: + # The init-certs container sends a certificate signing request to the + # kubernetes cluster. + # You can see pending requests using: kubectl get csr + # CSRs can be approved using: kubectl certificate approve + # + # All addresses used to contact a node must be specified in the --addresses arg. + # + # In addition to the node certificate and key, the init-certs entrypoint will symlink + # the cluster CA to the certs directory. + - name: init-certs + image: cockroachdb/cockroach-k8s-request-cert:0.2 + imagePullPolicy: IfNotPresent + command: + - "/bin/ash" + - "-ecx" + - "/request-cert -namespace=${POD_NAMESPACE} -certs-dir=/cockroach-certs -type=node -addresses=localhost,127.0.0.1,${POD_IP},$(hostname -f),$(hostname -f|cut -f 1-2 -d '.'),$(CDB_PUBLIC_SVC) -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: certs + mountPath: /cockroach-certs + + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - cockroachdb + topologyKey: kubernetes.io/hostname + containers: + - name: cockroachdb + image: cockroachdb/cockroach:v1.1.5 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 26257 + name: grpc + - containerPort: 8080 + name: http + volumeMounts: + - name: datadir + mountPath: /cockroach/cockroach-data + - name: certs + mountPath: /cockroach/cockroach-certs + command: + - "/bin/bash" + - "-ecx" + # The use of qualified `hostname -f` is crucial: + # Other nodes aren't able to look up the unqualified hostname. + # Once 2.0 is out, we should be able to switch from --host to --advertise-host to make port-forwarding work to the main port. + - "exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs --host $(hostname -f) --http-host 0.0.0.0 --join $(CDB_STATEFULSET_NAME)-0.$(CDB_STATEFULSET_SVC),$(CDB_STATEFULSET_NAME)-1.$(CDB_STATEFULSET_SVC),$(CDB_STATEFULSET_NAME)-2.$(CDB_STATEFULSET_SVC) --cache 25% --max-sql-memory 25%" + # No pre-stop hook is required, a SIGTERM plus some time is all that's + # needed for graceful shutdown of a node. + terminationGracePeriodSeconds: 60 + volumes: + - name: datadir + persistentVolumeClaim: + claimName: datadir + - name: certs + emptyDir: {} + updateStrategy: + type: RollingUpdate + volumeClaimTemplates: + - metadata: + name: datadir + spec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi diff --git a/pkg/commands/testdata/testcase-variable-ref/in/package/kustomization.yaml b/pkg/commands/testdata/testcase-variable-ref/in/package/kustomization.yaml new file mode 100644 index 000000000..cf7e862bd --- /dev/null +++ b/pkg/commands/testdata/testcase-variable-ref/in/package/kustomization.yaml @@ -0,0 +1,26 @@ +namePrefix: base- +resources: + - cockroachdb-statefulset-secure.yaml +vars: + - name: CDB_PUBLIC_SVC + objref: + kind: Service + name: cockroachdb-public + apiVersion: v1 + fieldref: + fieldpath: metadata.name + - name: CDB_STATEFULSET_NAME + objref: + kind: StatefulSet + name: cockroachdb + apiVersion: apps/v1beta1 + fieldref: + fieldpath: metadata.name + - name: CDB_STATEFULSET_SVC + objref: + kind: Service + name: cockroachdb + apiVersion: v1 + fieldref: + fieldpath: metadata.name + diff --git a/pkg/commands/testdata/testcase-variable-ref/test.yaml b/pkg/commands/testdata/testcase-variable-ref/test.yaml new file mode 100644 index 000000000..0c94b121e --- /dev/null +++ b/pkg/commands/testdata/testcase-variable-ref/test.yaml @@ -0,0 +1,5 @@ +description: variable reference and substitution +args: [] +filename: testdata/testcase-variable-ref/in/overlay/ +expectedStdout: testdata/testcase-variable-ref/expected.yaml +expectedDiff: testdata/testcase-variable-ref/expected.diff diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index bf00a630f..eaf4cee30 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -19,6 +19,7 @@ package resource import ( "encoding/json" + "fmt" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -109,3 +110,29 @@ func newUnstructuredFromObject(in runtime.Object) (*unstructured.Unstructured, e err = out.UnmarshalJSON(marshaled) return &out, err } + +func GetFieldValue(m map[string]interface{}, pathToField []string) (string, error) { + if len(pathToField) == 0 { + return "", fmt.Errorf("Field not found") + } + + if len(pathToField) == 1 { + if v, found := m[pathToField[0]]; found { + if s, ok := v.(string); ok { + return s, nil + } + return "", fmt.Errorf("value at fieldpath is not of string type") + } + return "", fmt.Errorf("field at given fieldpath does not exist") + } + + curr, rest := pathToField[0], pathToField[1] + + v := m[curr] + switch typedV := v.(type) { + case map[string]interface{}: + return GetFieldValue(typedV, []string{rest}) + default: + return "", fmt.Errorf("%#v is not expected to be a primitive type", typedV) + } +} diff --git a/pkg/resource/resource_test.go b/pkg/resource/resource_test.go new file mode 100644 index 000000000..4ccf9184e --- /dev/null +++ b/pkg/resource/resource_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2018 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 resource + +import ( + "testing" +) + +func TestGetFieldAsString(t *testing.T) { + m := map[string]interface{}{ + "Kind": "Service", + "metadata": map[string]interface{}{ + "labels": map[string]string{ + "app": "application-name", + }, + "name": "service-name", + }, + } + + tests := []struct { + pathToField []string + expectedName string + expectedError bool + }{ + { + pathToField: []string{"Kind"}, + expectedName: "Service", + expectedError: false, + }, + { + pathToField: []string{"metadata", "name"}, + expectedName: "service-name", + expectedError: false, + }, + { + pathToField: []string{"metadata", "non-existing-field"}, + expectedName: "", + expectedError: true, + }, + } + + for _, test := range tests { + s, err := GetFieldValue(m, test.pathToField) + if test.expectedError && err == nil { + t.Fatalf("should return error, but no error returned") + } else { + if test.expectedName != s { + t.Fatalf("Got:%s expected:%s", s, test.expectedName) + } + } + } +} diff --git a/pkg/transformers/refvars.go b/pkg/transformers/refvars.go new file mode 100644 index 000000000..b572178d0 --- /dev/null +++ b/pkg/transformers/refvars.go @@ -0,0 +1,82 @@ +package transformers + +import ( + "fmt" + + "github.com/kubernetes-sigs/kustomize/pkg/expansion" + "github.com/kubernetes-sigs/kustomize/pkg/resmap" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type refvarTransformer struct { + pathConfigs []PathConfig + vars map[string]string +} + +// NewRefVarTransformer returns a Trasformer that replaces $(VAR) style variables with values. +func NewRefVarTransformer(vars map[string]string) (Transformer, error) { + return &refvarTransformer{ + vars: vars, + pathConfigs: []PathConfig{ + { + GroupVersionKind: &schema.GroupVersionKind{Kind: "StatefulSet"}, + Path: []string{"spec", "template", "spec", "initContainers", "command"}, + }, + { + GroupVersionKind: &schema.GroupVersionKind{Kind: "StatefulSet"}, + Path: []string{"spec", "template", "spec", "containers", "command"}, + }, + { + GroupVersionKind: &schema.GroupVersionKind{Kind: "Job"}, + Path: []string{"spec", "template", "spec", "containers", "command"}, + }, + }, + }, nil +} + +// Transform determines the final values of variables: +// +// 1. Determine the final value of each variable: +// a. If the variable's Value is set, expand the `$(var)` references to other +// variables in the .Value field; the sources of variables are the declared +// variables of the container and the service environment variables +// b. If a source is defined for an environment variable, resolve the source +// 2. Create the container's environment in the order variables are declared +// 3. Add remaining service environment vars +func (rv *refvarTransformer) Transform(resources resmap.ResMap) error { + for GVKn := range resources { + obj := resources[GVKn].Unstruct() + objMap := obj.UnstructuredContent() + for _, pc := range rv.pathConfigs { + if !selectByGVK(GVKn.Gvk(), pc.GroupVersionKind) { + continue + } + err := mutateField(objMap, pc.Path, false, func(in interface{}) (interface{}, error) { + var ( + mappingFunc = expansion.MappingFuncFor(rv.vars) + ) + switch vt := in.(type) { + case []interface{}: + var xs []string + for _, a := range in.([]interface{}) { + xs = append(xs, expansion.Expand(a.(string), mappingFunc)) + } + return xs, nil + case interface{}: + s, ok := in.(string) + if !ok { + return nil, fmt.Errorf("%#v is expectd to be %T", in, s) + } + runtimeVal := expansion.Expand(s, mappingFunc) + return runtimeVal, nil + default: + return "", fmt.Errorf("invalid type encountered %T", vt) + } + }) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/types/kustomization.go b/pkg/types/kustomization.go index d5b9f0707..184d8e045 100644 --- a/pkg/types/kustomization.go +++ b/pkg/types/kustomization.go @@ -62,6 +62,9 @@ type Kustomization struct { // If a secret want to have a base and an overlay, it should go to Bases and // Overlays fields. SecretGenerator []SecretArgs `json:"secretGenerator,omitempty" yaml:"secretGenerator,omitempty"` + + // Variables which will be substituted at runtime + Vars []Var `json:"vars,omitempty" yaml:"vars,omitempty"` } // ConfigMapArg contains the metadata of how to generate a configmap. diff --git a/pkg/types/var.go b/pkg/types/var.go new file mode 100644 index 000000000..294d4d318 --- /dev/null +++ b/pkg/types/var.go @@ -0,0 +1,47 @@ +/* +Copyright 2017 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 types + +import ( + corev1 "k8s.io/api/core/v1" +) + +// Var represents a variable whose value will be sourced +// from a field in a Kubernetes object. +type Var struct { + // Value of identifier name e.g. FOO used in container args, annotations + // Appears in pod template as $(FOO) + Name string `json:"name" yaml:"name"` + + // ObjRef must refer to a Kubernetes resource under the + // purview of this kustomization. ObjRef should use the + // raw name of the object (the name specified in its YAML, + // before addition of a namePrefix). + ObjRef corev1.ObjectReference `json:"objref" yaml:"objref"` + + // FieldRef refers to the field of the object referred to by + // ObjRef whose value will be extracted for use in + // replacing $(FOO). + // If unspecified, this defaults to fieldpath: metadata.name + FieldRef corev1.ObjectFieldSelector `json:"fieldref,omitempty" yaml:"objref,omitempty"` +} + +func (v *Var) Defaulting() { + if (corev1.ObjectFieldSelector{}) == v.FieldRef { + v.FieldRef = corev1.ObjectFieldSelector{FieldPath: "metadata.name"} + } +}