dhall-haskell
dhall-haskell copied to clipboard
yaml-to-dhall doesn't handle YAML 1.1 octal values correctly (used in Kubernetes)
yaml-to-dhall
seems to simply strip leading 0
's from octal values when converting them to dhall. This leads to problems when we need to render the dhall back to yaml again.
Consider the following Kubernetes Deployment yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
description: Collects metrics and aggregates them into graphs.
labels:
deploy: sourcegraph
sourcegraph-resource-requires: no-cluster-admin
name: prometheus
spec:
minReadySeconds: 10
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: prometheus
strategy:
type: Recreate
template:
metadata:
labels:
deploy: sourcegraph
app: prometheus
spec:
containers:
- image: index.docker.io/sourcegraph/prometheus:3.18.0@sha256:e970ed46bdf3f73477f95de9ada424ada4a24505239687ce0e474a62dac6c67b
terminationMessagePolicy: FallbackToLogsOnError
name: prometheus
readinessProbe:
httpGet:
path: /-/ready
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
livenessProbe:
httpGet:
path: /-/healthy
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
ports:
- containerPort: 9090
name: http
volumeMounts:
- mountPath: /prometheus
name: data
- mountPath: /sg_prometheus_add_ons
name: config
# Prometheus is relied upon to monitor services for sending alerts to site admins when
# something is wrong with Sourcegraph, thus its memory requests and limits are the same to
# guarantee it has enough memory to perform its job reliably and prevent conflicts with
# other pods on the same host node.
#
# Its average memory usage may be much lower than 3G if Sourcegraph itself does not have
# much traffic, the 3G number chosen here is what works reliably on Sourcegraph.com with
# lots of traffic.
resources:
limits:
cpu: "2"
memory: 3G
requests:
cpu: 500m
memory: 3G
securityContext:
runAsUser: 0
serviceAccountName: prometheus
volumes:
- name: data
persistentVolumeClaim:
claimName: prometheus
- configMap:
defaultMode: 0777
name: prometheus
name: config
Note the defaultMode
of the configMap, 0777
(octal)
- configMap:
defaultMode: 0777
name: prometheus
name: config
Converting it to dhall results in the following output
> yaml-to-dhall --version
1.2.1
> pbpaste | yaml-to-dhall '( https://raw.githubusercontent.com/dhall-lang/dhall-kubernetes/f4bf4b9ddf669f7149ec32150863a93d6c4b3ef1/1.18/schemas/io.k8s.api.apps.v1.Deployment.dhall ).Type'
{ apiVersion = "apps/v1"
, kind = "Deployment"
, metadata =
{ annotations = Some
( toMap
{ description = "Collects metrics and aggregates them into graphs." }
)
, clusterName = None Text
, creationTimestamp = None Text
, deletionGracePeriodSeconds = None Natural
, deletionTimestamp = None Text
, finalizers = None (List Text)
, generateName = None Text
, generation = None Natural
, labels = Some
( toMap
{ sourcegraph-resource-requires = "no-cluster-admin"
, deploy = "sourcegraph"
}
)
, managedFields =
None
( List
{ apiVersion : Text
, fieldsType : Optional Text
, fieldsV1 : Optional (List { mapKey : Text, mapValue : Text })
, manager : Optional Text
, operation : Optional Text
, time : Optional Text
}
)
, name = Some "prometheus"
, namespace = None Text
, ownerReferences =
None
( List
{ apiVersion : Text
, blockOwnerDeletion : Optional Bool
, controller : Optional Bool
, kind : Text
, name : Text
, uid : Text
}
)
, resourceVersion = None Text
, selfLink = None Text
, uid = None Text
}
, spec = Some
{ minReadySeconds = Some 10
, paused = None Bool
, progressDeadlineSeconds = None Natural
, replicas = Some 1
, revisionHistoryLimit = Some 10
, selector =
{ matchExpressions =
None
(List { key : Text, operator : Text, values : Optional (List Text) })
, matchLabels = Some (toMap { app = "prometheus" })
}
, strategy = Some
{ rollingUpdate =
None
{ maxSurge : Optional < Int : Natural | String : Text >
, maxUnavailable : Optional < Int : Natural | String : Text >
}
, type = Some "Recreate"
}
, template =
{ metadata =
{ annotations = None (List { mapKey : Text, mapValue : Text })
, clusterName = None Text
, creationTimestamp = None Text
, deletionGracePeriodSeconds = None Natural
, deletionTimestamp = None Text
, finalizers = None (List Text)
, generateName = None Text
, generation = None Natural
, labels = Some (toMap { app = "prometheus", deploy = "sourcegraph" })
, managedFields =
None
( List
{ apiVersion : Text
, fieldsType : Optional Text
, fieldsV1 : Optional (List { mapKey : Text, mapValue : Text })
, manager : Optional Text
, operation : Optional Text
, time : Optional Text
}
)
, name = None Text
, namespace = None Text
, ownerReferences =
None
( List
{ apiVersion : Text
, blockOwnerDeletion : Optional Bool
, controller : Optional Bool
, kind : Text
, name : Text
, uid : Text
}
)
, resourceVersion = None Text
, selfLink = None Text
, uid = None Text
}
, spec = Some
{ activeDeadlineSeconds = None Natural
, affinity =
None
{ nodeAffinity :
Optional
{ preferredDuringSchedulingIgnoredDuringExecution :
Optional
( List
{ preference :
{ matchExpressions :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
, matchFields :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
}
, weight : Natural
}
)
, requiredDuringSchedulingIgnoredDuringExecution :
Optional
{ nodeSelectorTerms :
List
{ matchExpressions :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
, matchFields :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
}
}
}
, podAffinity :
Optional
{ preferredDuringSchedulingIgnoredDuringExecution :
Optional
( List
{ podAffinityTerm :
{ labelSelector :
Optional
{ matchExpressions :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
, matchLabels :
Optional
( List
{ mapKey : Text
, mapValue : Text
}
)
}
, namespaces : Optional (List Text)
, topologyKey : Text
}
, weight : Natural
}
)
, requiredDuringSchedulingIgnoredDuringExecution :
Optional
( List
{ labelSelector :
Optional
{ matchExpressions :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
, matchLabels :
Optional
( List
{ mapKey : Text, mapValue : Text }
)
}
, namespaces : Optional (List Text)
, topologyKey : Text
}
)
}
, podAntiAffinity :
Optional
{ preferredDuringSchedulingIgnoredDuringExecution :
Optional
( List
{ podAffinityTerm :
{ labelSelector :
Optional
{ matchExpressions :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
, matchLabels :
Optional
( List
{ mapKey : Text
, mapValue : Text
}
)
}
, namespaces : Optional (List Text)
, topologyKey : Text
}
, weight : Natural
}
)
, requiredDuringSchedulingIgnoredDuringExecution :
Optional
( List
{ labelSelector :
Optional
{ matchExpressions :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
, matchLabels :
Optional
( List
{ mapKey : Text, mapValue : Text }
)
}
, namespaces : Optional (List Text)
, topologyKey : Text
}
)
}
}
, automountServiceAccountToken = None Bool
, containers =
[ { args = None (List Text)
, command = None (List Text)
, env =
None
( List
{ name : Text
, value : Optional Text
, valueFrom :
Optional
{ configMapKeyRef :
Optional
{ key : Text
, name : Optional Text
, optional : Optional Bool
}
, fieldRef :
Optional
{ apiVersion : Optional Text, fieldPath : Text }
, resourceFieldRef :
Optional
{ containerName : Optional Text
, divisor : Optional Text
, resource : Text
}
, secretKeyRef :
Optional
{ key : Text
, name : Optional Text
, optional : Optional Bool
}
}
}
)
, envFrom =
None
( List
{ configMapRef :
Optional
{ name : Optional Text, optional : Optional Bool }
, prefix : Optional Text
, secretRef :
Optional
{ name : Optional Text, optional : Optional Bool }
}
)
, image = Some
"index.docker.io/sourcegraph/prometheus:3.18.0@sha256:e970ed46bdf3f73477f95de9ada424ada4a24505239687ce0e474a62dac6c67b"
, imagePullPolicy = None Text
, lifecycle =
None
{ postStart :
Optional
{ exec : Optional { command : Optional (List Text) }
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
}
, preStop :
Optional
{ exec : Optional { command : Optional (List Text) }
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
}
}
, livenessProbe = Some
{ exec = None { command : Optional (List Text) }
, failureThreshold = None Natural
, httpGet = Some
{ host = None Text
, httpHeaders = None (List { name : Text, value : Text })
, path = Some "/-/healthy"
, port = < Int : Natural | String : Text >.Int 9090
, scheme = None Text
}
, initialDelaySeconds = Some 30
, periodSeconds = None Natural
, successThreshold = None Natural
, tcpSocket =
None
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds = Some 30
}
, name = "prometheus"
, ports = Some
[ { containerPort = 9090
, hostIP = None Text
, hostPort = None Natural
, name = Some "http"
, protocol = None Text
}
]
, readinessProbe = Some
{ exec = None { command : Optional (List Text) }
, failureThreshold = None Natural
, httpGet = Some
{ host = None Text
, httpHeaders = None (List { name : Text, value : Text })
, path = Some "/-/ready"
, port = < Int : Natural | String : Text >.Int 9090
, scheme = None Text
}
, initialDelaySeconds = Some 30
, periodSeconds = None Natural
, successThreshold = None Natural
, tcpSocket =
None
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds = Some 30
}
, resources = Some
{ limits = Some (toMap { memory = "3G", cpu = "2" })
, requests = Some (toMap { memory = "3G", cpu = "500m" })
}
, securityContext =
None
{ allowPrivilegeEscalation : Optional Bool
, capabilities :
Optional
{ add : Optional (List Text)
, drop : Optional (List Text)
}
, privileged : Optional Bool
, procMount : Optional Text
, readOnlyRootFilesystem : Optional Bool
, runAsGroup : Optional Natural
, runAsNonRoot : Optional Bool
, runAsUser : Optional Natural
, seLinuxOptions :
Optional
{ level : Optional Text
, role : Optional Text
, type : Optional Text
, user : Optional Text
}
, windowsOptions :
Optional
{ gmsaCredentialSpec : Optional Text
, gmsaCredentialSpecName : Optional Text
, runAsUserName : Optional Text
}
}
, startupProbe =
None
{ exec : Optional { command : Optional (List Text) }
, failureThreshold : Optional Natural
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, initialDelaySeconds : Optional Natural
, periodSeconds : Optional Natural
, successThreshold : Optional Natural
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds : Optional Natural
}
, stdin = None Bool
, stdinOnce = None Bool
, terminationMessagePath = None Text
, terminationMessagePolicy = Some "FallbackToLogsOnError"
, tty = None Bool
, volumeDevices = None (List { devicePath : Text, name : Text })
, volumeMounts = Some
[ { mountPath = "/prometheus"
, mountPropagation = None Text
, name = "data"
, readOnly = None Bool
, subPath = None Text
, subPathExpr = None Text
}
, { mountPath = "/sg_prometheus_add_ons"
, mountPropagation = None Text
, name = "config"
, readOnly = None Bool
, subPath = None Text
, subPathExpr = None Text
}
]
, workingDir = None Text
}
]
, dnsConfig =
None
{ nameservers : Optional (List Text)
, options :
Optional (List { name : Optional Text, value : Optional Text })
, searches : Optional (List Text)
}
, dnsPolicy = None Text
, enableServiceLinks = None Bool
, ephemeralContainers =
None
( List
{ args : Optional (List Text)
, command : Optional (List Text)
, env :
Optional
( List
{ name : Text
, value : Optional Text
, valueFrom :
Optional
{ configMapKeyRef :
Optional
{ key : Text
, name : Optional Text
, optional : Optional Bool
}
, fieldRef :
Optional
{ apiVersion : Optional Text
, fieldPath : Text
}
, resourceFieldRef :
Optional
{ containerName : Optional Text
, divisor : Optional Text
, resource : Text
}
, secretKeyRef :
Optional
{ key : Text
, name : Optional Text
, optional : Optional Bool
}
}
}
)
, envFrom :
Optional
( List
{ configMapRef :
Optional
{ name : Optional Text
, optional : Optional Bool
}
, prefix : Optional Text
, secretRef :
Optional
{ name : Optional Text
, optional : Optional Bool
}
}
)
, image : Optional Text
, imagePullPolicy : Optional Text
, lifecycle :
Optional
{ postStart :
Optional
{ exec : Optional { command : Optional (List Text) }
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional
(List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
}
, preStop :
Optional
{ exec : Optional { command : Optional (List Text) }
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional
(List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
}
}
, livenessProbe :
Optional
{ exec : Optional { command : Optional (List Text) }
, failureThreshold : Optional Natural
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, initialDelaySeconds : Optional Natural
, periodSeconds : Optional Natural
, successThreshold : Optional Natural
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds : Optional Natural
}
, name : Text
, ports :
Optional
( List
{ containerPort : Natural
, hostIP : Optional Text
, hostPort : Optional Natural
, name : Optional Text
, protocol : Optional Text
}
)
, readinessProbe :
Optional
{ exec : Optional { command : Optional (List Text) }
, failureThreshold : Optional Natural
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, initialDelaySeconds : Optional Natural
, periodSeconds : Optional Natural
, successThreshold : Optional Natural
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds : Optional Natural
}
, resources :
Optional
{ limits :
Optional (List { mapKey : Text, mapValue : Text })
, requests :
Optional (List { mapKey : Text, mapValue : Text })
}
, securityContext :
Optional
{ allowPrivilegeEscalation : Optional Bool
, capabilities :
Optional
{ add : Optional (List Text)
, drop : Optional (List Text)
}
, privileged : Optional Bool
, procMount : Optional Text
, readOnlyRootFilesystem : Optional Bool
, runAsGroup : Optional Natural
, runAsNonRoot : Optional Bool
, runAsUser : Optional Natural
, seLinuxOptions :
Optional
{ level : Optional Text
, role : Optional Text
, type : Optional Text
, user : Optional Text
}
, windowsOptions :
Optional
{ gmsaCredentialSpec : Optional Text
, gmsaCredentialSpecName : Optional Text
, runAsUserName : Optional Text
}
}
, startupProbe :
Optional
{ exec : Optional { command : Optional (List Text) }
, failureThreshold : Optional Natural
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, initialDelaySeconds : Optional Natural
, periodSeconds : Optional Natural
, successThreshold : Optional Natural
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds : Optional Natural
}
, stdin : Optional Bool
, stdinOnce : Optional Bool
, targetContainerName : Optional Text
, terminationMessagePath : Optional Text
, terminationMessagePolicy : Optional Text
, tty : Optional Bool
, volumeDevices :
Optional (List { devicePath : Text, name : Text })
, volumeMounts :
Optional
( List
{ mountPath : Text
, mountPropagation : Optional Text
, name : Text
, readOnly : Optional Bool
, subPath : Optional Text
, subPathExpr : Optional Text
}
)
, workingDir : Optional Text
}
)
, hostAliases =
None (List { hostnames : Optional (List Text), ip : Optional Text })
, hostIPC = None Bool
, hostNetwork = None Bool
, hostPID = None Bool
, hostname = None Text
, imagePullSecrets = None (List { name : Optional Text })
, initContainers =
None
( List
{ args : Optional (List Text)
, command : Optional (List Text)
, env :
Optional
( List
{ name : Text
, value : Optional Text
, valueFrom :
Optional
{ configMapKeyRef :
Optional
{ key : Text
, name : Optional Text
, optional : Optional Bool
}
, fieldRef :
Optional
{ apiVersion : Optional Text
, fieldPath : Text
}
, resourceFieldRef :
Optional
{ containerName : Optional Text
, divisor : Optional Text
, resource : Text
}
, secretKeyRef :
Optional
{ key : Text
, name : Optional Text
, optional : Optional Bool
}
}
}
)
, envFrom :
Optional
( List
{ configMapRef :
Optional
{ name : Optional Text
, optional : Optional Bool
}
, prefix : Optional Text
, secretRef :
Optional
{ name : Optional Text
, optional : Optional Bool
}
}
)
, image : Optional Text
, imagePullPolicy : Optional Text
, lifecycle :
Optional
{ postStart :
Optional
{ exec : Optional { command : Optional (List Text) }
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional
(List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
}
, preStop :
Optional
{ exec : Optional { command : Optional (List Text) }
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional
(List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
}
}
, livenessProbe :
Optional
{ exec : Optional { command : Optional (List Text) }
, failureThreshold : Optional Natural
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, initialDelaySeconds : Optional Natural
, periodSeconds : Optional Natural
, successThreshold : Optional Natural
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds : Optional Natural
}
, name : Text
, ports :
Optional
( List
{ containerPort : Natural
, hostIP : Optional Text
, hostPort : Optional Natural
, name : Optional Text
, protocol : Optional Text
}
)
, readinessProbe :
Optional
{ exec : Optional { command : Optional (List Text) }
, failureThreshold : Optional Natural
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, initialDelaySeconds : Optional Natural
, periodSeconds : Optional Natural
, successThreshold : Optional Natural
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds : Optional Natural
}
, resources :
Optional
{ limits :
Optional (List { mapKey : Text, mapValue : Text })
, requests :
Optional (List { mapKey : Text, mapValue : Text })
}
, securityContext :
Optional
{ allowPrivilegeEscalation : Optional Bool
, capabilities :
Optional
{ add : Optional (List Text)
, drop : Optional (List Text)
}
, privileged : Optional Bool
, procMount : Optional Text
, readOnlyRootFilesystem : Optional Bool
, runAsGroup : Optional Natural
, runAsNonRoot : Optional Bool
, runAsUser : Optional Natural
, seLinuxOptions :
Optional
{ level : Optional Text
, role : Optional Text
, type : Optional Text
, user : Optional Text
}
, windowsOptions :
Optional
{ gmsaCredentialSpec : Optional Text
, gmsaCredentialSpecName : Optional Text
, runAsUserName : Optional Text
}
}
, startupProbe :
Optional
{ exec : Optional { command : Optional (List Text) }
, failureThreshold : Optional Natural
, httpGet :
Optional
{ host : Optional Text
, httpHeaders :
Optional (List { name : Text, value : Text })
, path : Optional Text
, port : < Int : Natural | String : Text >
, scheme : Optional Text
}
, initialDelaySeconds : Optional Natural
, periodSeconds : Optional Natural
, successThreshold : Optional Natural
, tcpSocket :
Optional
{ host : Optional Text
, port : < Int : Natural | String : Text >
}
, timeoutSeconds : Optional Natural
}
, stdin : Optional Bool
, stdinOnce : Optional Bool
, terminationMessagePath : Optional Text
, terminationMessagePolicy : Optional Text
, tty : Optional Bool
, volumeDevices :
Optional (List { devicePath : Text, name : Text })
, volumeMounts :
Optional
( List
{ mountPath : Text
, mountPropagation : Optional Text
, name : Text
, readOnly : Optional Bool
, subPath : Optional Text
, subPathExpr : Optional Text
}
)
, workingDir : Optional Text
}
)
, nodeName = None Text
, nodeSelector = None (List { mapKey : Text, mapValue : Text })
, overhead = None (List { mapKey : Text, mapValue : Text })
, preemptionPolicy = None Text
, priority = None Natural
, priorityClassName = None Text
, readinessGates = None (List { conditionType : Text })
, restartPolicy = None Text
, runtimeClassName = None Text
, schedulerName = None Text
, securityContext = Some
{ fsGroup = None Natural
, fsGroupChangePolicy = None Text
, runAsGroup = None Natural
, runAsNonRoot = None Bool
, runAsUser = Some 0
, seLinuxOptions =
None
{ level : Optional Text
, role : Optional Text
, type : Optional Text
, user : Optional Text
}
, supplementalGroups = None (List Natural)
, sysctls = None (List { name : Text, value : Text })
, windowsOptions =
None
{ gmsaCredentialSpec : Optional Text
, gmsaCredentialSpecName : Optional Text
, runAsUserName : Optional Text
}
}
, serviceAccount = None Text
, serviceAccountName = Some "prometheus"
, shareProcessNamespace = None Bool
, subdomain = None Text
, terminationGracePeriodSeconds = None Natural
, tolerations =
None
( List
{ effect : Optional Text
, key : Optional Text
, operator : Optional Text
, tolerationSeconds : Optional Natural
, value : Optional Text
}
)
, topologySpreadConstraints =
None
( List
{ labelSelector :
Optional
{ matchExpressions :
Optional
( List
{ key : Text
, operator : Text
, values : Optional (List Text)
}
)
, matchLabels :
Optional (List { mapKey : Text, mapValue : Text })
}
, maxSkew : Natural
, topologyKey : Text
, whenUnsatisfiable : Text
}
)
, volumes = Some
[ { awsElasticBlockStore =
None
{ fsType : Optional Text
, partition : Optional Natural
, readOnly : Optional Bool
, volumeID : Text
}
, azureDisk =
None
{ cachingMode : Optional Text
, diskName : Text
, diskURI : Text
, fsType : Optional Text
, kind : Text
, readOnly : Optional Bool
}
, azureFile =
None
{ readOnly : Optional Bool
, secretName : Text
, shareName : Text
}
, cephfs =
None
{ monitors : List Text
, path : Optional Text
, readOnly : Optional Bool
, secretFile : Optional Text
, secretRef : Optional { name : Optional Text }
, user : Optional Text
}
, cinder =
None
{ fsType : Optional Text
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
, volumeID : Text
}
, configMap =
None
{ defaultMode : Optional Natural
, items :
Optional
( List
{ key : Text, mode : Optional Natural, path : Text }
)
, name : Optional Text
, optional : Optional Bool
}
, csi =
None
{ driver : Text
, fsType : Optional Text
, nodePublishSecretRef : Optional { name : Optional Text }
, readOnly : Optional Bool
, volumeAttributes :
Optional (List { mapKey : Text, mapValue : Text })
}
, downwardAPI =
None
{ defaultMode : Optional Natural
, items :
Optional
( List
{ fieldRef :
Optional
{ apiVersion : Optional Text, fieldPath : Text }
, mode : Optional Natural
, path : Text
, resourceFieldRef :
Optional
{ containerName : Optional Text
, divisor : Optional Text
, resource : Text
}
}
)
}
, emptyDir =
None { medium : Optional Text, sizeLimit : Optional Text }
, fc =
None
{ fsType : Optional Text
, lun : Optional Natural
, readOnly : Optional Bool
, targetWWNs : Optional (List Text)
, wwids : Optional (List Text)
}
, flexVolume =
None
{ driver : Text
, fsType : Optional Text
, options : Optional (List { mapKey : Text, mapValue : Text })
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
}
, flocker =
None { datasetName : Optional Text, datasetUUID : Optional Text }
, gcePersistentDisk =
None
{ fsType : Optional Text
, partition : Optional Natural
, pdName : Text
, readOnly : Optional Bool
}
, gitRepo =
None
{ directory : Optional Text
, repository : Text
, revision : Optional Text
}
, glusterfs =
None { endpoints : Text, path : Text, readOnly : Optional Bool }
, hostPath = None { path : Text, type : Optional Text }
, iscsi =
None
{ chapAuthDiscovery : Optional Bool
, chapAuthSession : Optional Bool
, fsType : Optional Text
, initiatorName : Optional Text
, iqn : Text
, iscsiInterface : Optional Text
, lun : Natural
, portals : Optional (List Text)
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
, targetPortal : Text
}
, name = "data"
, nfs = None { path : Text, readOnly : Optional Bool, server : Text }
, persistentVolumeClaim = Some
{ claimName = "prometheus", readOnly = None Bool }
, photonPersistentDisk = None { fsType : Optional Text, pdID : Text }
, portworxVolume =
None
{ fsType : Optional Text
, readOnly : Optional Bool
, volumeID : Text
}
, projected =
None
{ defaultMode : Optional Natural
, sources :
List
{ configMap :
Optional
{ items :
Optional
( List
{ key : Text
, mode : Optional Natural
, path : Text
}
)
, name : Optional Text
, optional : Optional Bool
}
, downwardAPI :
Optional
{ items :
Optional
( List
{ fieldRef :
Optional
{ apiVersion : Optional Text
, fieldPath : Text
}
, mode : Optional Natural
, path : Text
, resourceFieldRef :
Optional
{ containerName : Optional Text
, divisor : Optional Text
, resource : Text
}
}
)
}
, secret :
Optional
{ items :
Optional
( List
{ key : Text
, mode : Optional Natural
, path : Text
}
)
, name : Optional Text
, optional : Optional Bool
}
, serviceAccountToken :
Optional
{ audience : Optional Text
, expirationSeconds : Optional Natural
, path : Text
}
}
}
, quobyte =
None
{ group : Optional Text
, readOnly : Optional Bool
, registry : Text
, tenant : Optional Text
, user : Optional Text
, volume : Text
}
, rbd =
None
{ fsType : Optional Text
, image : Text
, keyring : Optional Text
, monitors : List Text
, pool : Optional Text
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
, user : Optional Text
}
, scaleIO =
None
{ fsType : Optional Text
, gateway : Text
, protectionDomain : Optional Text
, readOnly : Optional Bool
, secretRef : { name : Optional Text }
, sslEnabled : Optional Bool
, storageMode : Optional Text
, storagePool : Optional Text
, system : Text
, volumeName : Optional Text
}
, secret =
None
{ defaultMode : Optional Natural
, items :
Optional
( List
{ key : Text, mode : Optional Natural, path : Text }
)
, optional : Optional Bool
, secretName : Optional Text
}
, storageos =
None
{ fsType : Optional Text
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
, volumeName : Optional Text
, volumeNamespace : Optional Text
}
, vsphereVolume =
None
{ fsType : Optional Text
, storagePolicyID : Optional Text
, storagePolicyName : Optional Text
, volumePath : Text
}
}
, { awsElasticBlockStore =
None
{ fsType : Optional Text
, partition : Optional Natural
, readOnly : Optional Bool
, volumeID : Text
}
, azureDisk =
None
{ cachingMode : Optional Text
, diskName : Text
, diskURI : Text
, fsType : Optional Text
, kind : Text
, readOnly : Optional Bool
}
, azureFile =
None
{ readOnly : Optional Bool
, secretName : Text
, shareName : Text
}
, cephfs =
None
{ monitors : List Text
, path : Optional Text
, readOnly : Optional Bool
, secretFile : Optional Text
, secretRef : Optional { name : Optional Text }
, user : Optional Text
}
, cinder =
None
{ fsType : Optional Text
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
, volumeID : Text
}
, configMap = Some
{ defaultMode = Some 777
, items =
None (List { key : Text, mode : Optional Natural, path : Text })
, name = Some "prometheus"
, optional = None Bool
}
, csi =
None
{ driver : Text
, fsType : Optional Text
, nodePublishSecretRef : Optional { name : Optional Text }
, readOnly : Optional Bool
, volumeAttributes :
Optional (List { mapKey : Text, mapValue : Text })
}
, downwardAPI =
None
{ defaultMode : Optional Natural
, items :
Optional
( List
{ fieldRef :
Optional
{ apiVersion : Optional Text, fieldPath : Text }
, mode : Optional Natural
, path : Text
, resourceFieldRef :
Optional
{ containerName : Optional Text
, divisor : Optional Text
, resource : Text
}
}
)
}
, emptyDir =
None { medium : Optional Text, sizeLimit : Optional Text }
, fc =
None
{ fsType : Optional Text
, lun : Optional Natural
, readOnly : Optional Bool
, targetWWNs : Optional (List Text)
, wwids : Optional (List Text)
}
, flexVolume =
None
{ driver : Text
, fsType : Optional Text
, options : Optional (List { mapKey : Text, mapValue : Text })
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
}
, flocker =
None { datasetName : Optional Text, datasetUUID : Optional Text }
, gcePersistentDisk =
None
{ fsType : Optional Text
, partition : Optional Natural
, pdName : Text
, readOnly : Optional Bool
}
, gitRepo =
None
{ directory : Optional Text
, repository : Text
, revision : Optional Text
}
, glusterfs =
None { endpoints : Text, path : Text, readOnly : Optional Bool }
, hostPath = None { path : Text, type : Optional Text }
, iscsi =
None
{ chapAuthDiscovery : Optional Bool
, chapAuthSession : Optional Bool
, fsType : Optional Text
, initiatorName : Optional Text
, iqn : Text
, iscsiInterface : Optional Text
, lun : Natural
, portals : Optional (List Text)
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
, targetPortal : Text
}
, name = "config"
, nfs = None { path : Text, readOnly : Optional Bool, server : Text }
, persistentVolumeClaim =
None { claimName : Text, readOnly : Optional Bool }
, photonPersistentDisk = None { fsType : Optional Text, pdID : Text }
, portworxVolume =
None
{ fsType : Optional Text
, readOnly : Optional Bool
, volumeID : Text
}
, projected =
None
{ defaultMode : Optional Natural
, sources :
List
{ configMap :
Optional
{ items :
Optional
( List
{ key : Text
, mode : Optional Natural
, path : Text
}
)
, name : Optional Text
, optional : Optional Bool
}
, downwardAPI :
Optional
{ items :
Optional
( List
{ fieldRef :
Optional
{ apiVersion : Optional Text
, fieldPath : Text
}
, mode : Optional Natural
, path : Text
, resourceFieldRef :
Optional
{ containerName : Optional Text
, divisor : Optional Text
, resource : Text
}
}
)
}
, secret :
Optional
{ items :
Optional
( List
{ key : Text
, mode : Optional Natural
, path : Text
}
)
, name : Optional Text
, optional : Optional Bool
}
, serviceAccountToken :
Optional
{ audience : Optional Text
, expirationSeconds : Optional Natural
, path : Text
}
}
}
, quobyte =
None
{ group : Optional Text
, readOnly : Optional Bool
, registry : Text
, tenant : Optional Text
, user : Optional Text
, volume : Text
}
, rbd =
None
{ fsType : Optional Text
, image : Text
, keyring : Optional Text
, monitors : List Text
, pool : Optional Text
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
, user : Optional Text
}
, scaleIO =
None
{ fsType : Optional Text
, gateway : Text
, protectionDomain : Optional Text
, readOnly : Optional Bool
, secretRef : { name : Optional Text }
, sslEnabled : Optional Bool
, storageMode : Optional Text
, storagePool : Optional Text
, system : Text
, volumeName : Optional Text
}
, secret =
None
{ defaultMode : Optional Natural
, items :
Optional
( List
{ key : Text, mode : Optional Natural, path : Text }
)
, optional : Optional Bool
, secretName : Optional Text
}
, storageos =
None
{ fsType : Optional Text
, readOnly : Optional Bool
, secretRef : Optional { name : Optional Text }
, volumeName : Optional Text
, volumeNamespace : Optional Text
}
, vsphereVolume =
None
{ fsType : Optional Text
, storagePolicyID : Optional Text
, storagePolicyName : Optional Text
, volumePath : Text
}
}
]
}
}
}
, status =
None
{ availableReplicas : Optional Natural
, collisionCount : Optional Natural
, conditions :
Optional
( List
{ lastTransitionTime : Optional Text
, lastUpdateTime : Optional Text
, message : Optional Text
, reason : Optional Text
, status : Text
, type : Text
}
)
, observedGeneration : Optional Natural
, readyReplicas : Optional Natural
, replicas : Optional Natural
, unavailableReplicas : Optional Natural
, updatedReplicas : Optional Natural
}
}
Note that defaultMode
of the configMap is now 777
(natural) in the dhall output
configMap = Some
{ defaultMode = Some 777
, items =
None (List { key : Text, mode : Optional Natural, path : Text })
, name = Some "prometheus"
, optional = None Bool
}
Converting that dhall back into kubernetes yaml:
> pbpaste | dhall-to-yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
description: Collects metrics and aggregates them into graphs.
labels:
deploy: sourcegraph
sourcegraph-resource-requires: no-cluster-admin
name: prometheus
spec:
minReadySeconds: 10
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: prometheus
strategy:
type: Recreate
template:
metadata:
labels:
app: prometheus
deploy: sourcegraph
spec:
containers:
- image: "index.docker.io/sourcegraph/prometheus:3.18.0@sha256:e970ed46bdf3f73477f95de9ada424ada4a24505239687ce0e474a62dac6c67b"
livenessProbe:
httpGet:
path: /-/healthy
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
name: prometheus
ports:
- containerPort: 9090
name: http
readinessProbe:
httpGet:
path: /-/ready
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
resources:
limits:
cpu: '2'
memory: "3G"
requests:
cpu: "500m"
memory: "3G"
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- mountPath: /prometheus
name: data
- mountPath: /sg_prometheus_add_ons
name: config
securityContext:
runAsUser: 0
serviceAccountName: prometheus
volumes:
- name: data
persistentVolumeClaim:
claimName: prometheus
- configMap:
defaultMode: 777
name: prometheus
name: config
Note that the configMap's defaultMode has changed from 0777
(octal) in the original yaml to 777
(decimal) in the new yaml.
And indeed, trying to apply this yaml into a cluster results in an error
> kubectl apply -f test.yaml
The Deployment "prometheus" is invalid:
* spec.template.spec.volumes[1].configMap.defaultMode: Invalid value: 777: must be a number between 0 and 0777 (octal), both inclusive
Octal values are an official part of the YAML spec, and it'd be nice if Dhall supported this too (or at least had some sort of visible warning). I am not sure what the fix should be (an official octal number type, yaml-to-dhall
converting octal values to the equivalent decimal values, or something else).
Octal values are an official part of the YAML spec
That's a reference for YAML 1.1, but yaml-to-dhall
's YAML parser (primarily?) tries to comply with YAML 1.2, where octal notation apparently requires a 0o
prefix, not just 0
.
Good to know, but apparently kubectl
(or the Kubernetes API server) supports the YAML 1.1 syntax. The official K8s documentation also uses the 0
prefix octal notation: https://kubernetes.io/docs/concepts/configuration/secret/#secret-files-permissions
The nature of these silent transformations also makes it difficult to detect / workaround
Based on the discussion so far it sounds like this can be split into two separate questions:
-
Should Dhall have language support for octal literals?
This is more of a question for the
dhall-lang
repository, but we can still briefly share our thoughts here -
Should
yaml-to-dhall
support YAML 1.1 octal literal syntaxThis question is appropriate for this repository
For the former question, I would be fine with standardizing support for octal literals for the language
For the latter question, I think it depends on two things:
-
Does
kubectl
support the newer0o…
octal syntax? (It sounds like it doesn't, but I just want to confirm) -
Does
kubectl
support non-octal numeric literals for permissions? (Based on a quick Google search it sounds like it does)
In general, adding support for YAML 1.1 octal literals might be problematic for us since the upstream HsYAML
package that yaml-to-dhall
is based appears to only supports YAML 1.2, which is why I'm exploring workarounds.
Does kubectl support the newer 0o… octal syntax? (It sounds like it doesn't, but I just want to confirm)
I think it does (at least from my brief test with the following versions):
> kubectl version
Client Version: version.Info{Major:"1", Minor:"16+", GitVersion:"v1.16.6-beta.0", GitCommit:"e7f962ba86f4ce7033828210ca3556393c377bcc", GitTreeState:"clean", BuildDate:"2020-01-15T08:26:26Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"14+", GitVersion:"v1.14.10-gke.42", GitCommit:"42bef28c2031a74fc68840fce56834ff7ea08518", GitTreeState:"clean", BuildDate:"2020-06-02T16:07:00Z", GoVersion:"go1.12.12b4", Compiler:"gc", Platform:"linux/amd64"}
I don't know if support for this was always there or if this was a recent change (or even if it varies between cloud providers).
Does kubectl support non-octal numeric literals for permissions? (Based on a quick Google search it sounds like it does)
Yes it does (you're able to specify defaultMode: 511
directly and it works as you expect).
I'd still like to encourage support for YAML 1.1 octal literals because that's the only style that I've seen "in the wild" for Kubernetes (probably because that's what's used in the official docs). I'm going to guess that a fair amount of people (maybe most?) use the this style when writing their manifests, so they're likely to be bitten by this when they want to use yaml-to-dhall
.
As a data point from the https://github.com/helm/charts repository:
-
I found
60+
examples of manifests using the YAML 1.1 octal notation style (0...
): https://sourcegraph.com/search?q=repo:%5Egithub%5C.com/helm/charts%24++%28mode%7CdefaultMode%29:+0%5Cd%2B+lang:yaml+++&patternType=regexp -
I couldn't find any examples of manifests using the YAML 1.2 octal notation style (
0o...
): https://sourcegraph.com/search?q=repo:%5Egithub%5C.com/helm/charts%24++%28mode%7CdefaultMode%29:+0o%5Cd%2B+lang:yaml+++&patternType=regexp
- Should Dhall have language support for octal literals?
This question seems only tangentially related to this issue to me?! I wouldn't mind if support for octal literals would be added though.
- Should
yaml-to-dhall
support YAML 1.1 octal literal syntax
This seems tricky, since IIUC these literals have different meaning in YAML 1.1 vs. 1.2. For example 010
is 8
with YAML 1.1, but 10
with 1.2.
It would be nice if yaml-to-dhall
would have a --yaml-1.1
option or something like that, I guess.
Or maybe there's a YAML-1.1 to YAML-1.2 converter that we could recommend for use with yaml-to-dhall
?!
@sjakobi: I like the idea of a YAML 1.1 to YAML 1.2 converter
The reason I suggested Dhall support for octal literal syntax was to ensure that the octal literal would be preserved when converting back from Dhall to YAML
Or maybe there's a YAML-1.1 to YAML-1.2 converter that we could recommend for use with
yaml-to-dhall
?!
I have created a SO question about this: https://stackoverflow.com/questions/63472830/how-do-i-convert-yaml-1-1-to-yaml-1-2
While doing so, I remembered that YAML 1.2 is a superset of JSON.
So, to prepare a YAML 1.1 document for consumption by yaml-to-dhall
, you can convert it to JSON, for example with this yml2json
utility (which, mind you, I have only tested once, not properly vetted). Instead of yaml-to-dhall
, you can also use json-to-dhall
then.
I have opened https://github.com/dhall-lang/dhall-lang/issues/1058 to discuss language support for octal numerals.