`linkerd upgrade config` bombs when installation had a flag (#3097)

When installing using some of the flags that persist in install, e.g
`linkerd install --ha`, and then doing `linkerd upgrade config` a nil
pointer error is thrown.

Fixes #3094

`newCmdUpgradeConfig()` was using passing `flags` as nil because
`linkerd upgrade config` doesn't expose any flags for the subcommand,
but turns out they're still needed down the call stack in
`setFlagsFromInstall` to reuse the flags persisted during install.

I also added a new unit test catching this.

Signed-off-by: Alejandro Pedraza <alejandro@buoyant.io>
This commit is contained in:
Alejandro Pedraza 2019-07-18 09:09:01 -05:00 committed by GitHub
parent ee1a111993
commit ba9fd70892
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 666 additions and 2 deletions

View File

@ -0,0 +1,622 @@
---
###
### Linkerd Namespace
###
---
kind: Namespace
apiVersion: v1
metadata:
name: linkerd
annotations:
linkerd.io/inject: disabled
labels:
linkerd.io/is-control-plane: "true"
---
###
### Identity Controller Service RBAC
###
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-linkerd-identity
labels:
linkerd.io/control-plane-component: identity
linkerd.io/control-plane-ns: linkerd
rules:
- apiGroups: ["authentication.k8s.io"]
resources: ["tokenreviews"]
verbs: ["create"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-linkerd-identity
labels:
linkerd.io/control-plane-component: identity
linkerd.io/control-plane-ns: linkerd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: linkerd-linkerd-identity
subjects:
- kind: ServiceAccount
name: linkerd-identity
namespace: linkerd
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-identity
namespace: linkerd
labels:
linkerd.io/control-plane-component: identity
linkerd.io/control-plane-ns: linkerd
---
###
### Controller RBAC
###
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-linkerd-controller
labels:
linkerd.io/control-plane-component: controller
linkerd.io/control-plane-ns: linkerd
rules:
- apiGroups: ["extensions", "apps"]
resources: ["daemonsets", "deployments", "replicasets", "statefulsets"]
verbs: ["list", "get", "watch"]
- apiGroups: ["extensions", "batch"]
resources: ["jobs"]
verbs: ["list" , "get", "watch"]
- apiGroups: [""]
resources: ["pods", "endpoints", "services", "replicationcontrollers", "namespaces"]
verbs: ["list", "get", "watch"]
- apiGroups: ["linkerd.io"]
resources: ["serviceprofiles"]
verbs: ["list", "get", "watch"]
- apiGroups: ["split.smi-spec.io"]
resources: ["trafficsplits"]
verbs: ["list", "get", "watch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-linkerd-controller
labels:
linkerd.io/control-plane-component: controller
linkerd.io/control-plane-ns: linkerd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: linkerd-linkerd-controller
subjects:
- kind: ServiceAccount
name: linkerd-controller
namespace: linkerd
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-controller
namespace: linkerd
labels:
linkerd.io/control-plane-component: controller
linkerd.io/control-plane-ns: linkerd
---
###
### Web RBAC
###
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-web
namespace: linkerd
labels:
linkerd.io/control-plane-component: web
linkerd.io/control-plane-ns: linkerd
---
###
### Service Profile CRD
###
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: serviceprofiles.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
linkerd.io/control-plane-ns: linkerd
spec:
group: linkerd.io
version: v1alpha1
scope: Namespaced
names:
plural: serviceprofiles
singular: serviceprofile
kind: ServiceProfile
shortNames:
- sp
validation:
openAPIV3Schema:
properties:
spec:
required:
- routes
properties:
retryBudget:
required:
- minRetriesPerSecond
- retryRatio
- ttl
type: object
properties:
minRetriesPerSecond:
type: integer
retryRatio:
type: number
ttl:
type: string
routes:
type: array
items:
type: object
required:
- name
- condition
properties:
name:
type: string
timeout:
type: string
condition:
type: object
minProperties: 1
properties:
method:
type: string
pathRegex:
type: string
all:
type: array
items:
type: object
any:
type: array
items:
type: object
not:
type: object
responseClasses:
type: array
items:
type: object
required:
- condition
properties:
isFailure:
type: boolean
condition:
type: object
properties:
status:
type: object
minProperties: 1
properties:
min:
type: integer
minimum: 100
maximum: 599
max:
type: integer
minimum: 100
maximum: 599
all:
type: array
items:
type: object
any:
type: array
items:
type: object
not:
type: object
---
###
### TrafficSplit CRD
### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
###
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: trafficsplits.split.smi-spec.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
linkerd.io/control-plane-ns: linkerd
spec:
group: split.smi-spec.io
version: v1alpha1
scope: Namespaced
names:
kind: TrafficSplit
shortNames:
- ts
plural: trafficsplits
singular: trafficsplit
additionalPrinterColumns:
- name: Service
type: string
description: The apex service of this split.
JSONPath: .spec.service
---
###
### Prometheus RBAC
###
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-linkerd-prometheus
labels:
linkerd.io/control-plane-component: prometheus
linkerd.io/control-plane-ns: linkerd
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-linkerd-prometheus
labels:
linkerd.io/control-plane-component: prometheus
linkerd.io/control-plane-ns: linkerd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: linkerd-linkerd-prometheus
subjects:
- kind: ServiceAccount
name: linkerd-prometheus
namespace: linkerd
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-prometheus
namespace: linkerd
labels:
linkerd.io/control-plane-component: prometheus
linkerd.io/control-plane-ns: linkerd
---
###
### Grafana RBAC
###
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-grafana
namespace: linkerd
labels:
linkerd.io/control-plane-component: grafana
linkerd.io/control-plane-ns: linkerd
---
###
### Proxy Injector RBAC
###
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: linkerd-linkerd-proxy-injector
labels:
linkerd.io/control-plane-component: proxy-injector
linkerd.io/control-plane-ns: linkerd
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["list", "get", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
- apiGroups: ["apps"]
resources: ["replicasets"]
verbs: ["list", "get", "watch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: linkerd-linkerd-proxy-injector
labels:
linkerd.io/control-plane-component: proxy-injector
linkerd.io/control-plane-ns: linkerd
subjects:
- kind: ServiceAccount
name: linkerd-proxy-injector
namespace: linkerd
apiGroup: ""
roleRef:
kind: ClusterRole
name: linkerd-linkerd-proxy-injector
apiGroup: rbac.authorization.k8s.io
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-proxy-injector
namespace: linkerd
labels:
linkerd.io/control-plane-component: proxy-injector
linkerd.io/control-plane-ns: linkerd
---
kind: Secret
apiVersion: v1
metadata:
name: linkerd-proxy-injector-tls
namespace: linkerd
labels:
linkerd.io/control-plane-component: proxy-injector
linkerd.io/control-plane-ns: linkerd
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
type: Opaque
data:
crt.pem: cHJveHkgaW5qZWN0b3IgY3J0
key.pem: cHJveHkgaW5qZWN0b3Iga2V5
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: linkerd-proxy-injector-webhook-config
labels:
linkerd.io/control-plane-component: proxy-injector
linkerd.io/control-plane-ns: linkerd
webhooks:
- name: linkerd-proxy-injector.linkerd.io
namespaceSelector:
matchExpressions:
- key: linkerd.io/is-control-plane
operator: DoesNotExist
clientConfig:
service:
name: linkerd-proxy-injector
namespace: linkerd
path: "/"
caBundle: cHJveHkgaW5qZWN0b3IgY3J0
failurePolicy: Fail
rules:
- operations: [ "CREATE" ]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
sideEffects: None
---
###
### Service Profile Validator RBAC
###
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: linkerd-linkerd-sp-validator
labels:
linkerd.io/control-plane-component: sp-validator
linkerd.io/control-plane-ns: linkerd
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: linkerd-linkerd-sp-validator
labels:
linkerd.io/control-plane-component: sp-validator
linkerd.io/control-plane-ns: linkerd
subjects:
- kind: ServiceAccount
name: linkerd-sp-validator
namespace: linkerd
apiGroup: ""
roleRef:
kind: ClusterRole
name: linkerd-linkerd-sp-validator
apiGroup: rbac.authorization.k8s.io
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-sp-validator
namespace: linkerd
labels:
linkerd.io/control-plane-component: sp-validator
linkerd.io/control-plane-ns: linkerd
---
kind: Secret
apiVersion: v1
metadata:
name: linkerd-sp-validator-tls
namespace: linkerd
labels:
linkerd.io/control-plane-component: sp-validator
linkerd.io/control-plane-ns: linkerd
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
type: Opaque
data:
crt.pem: cHJvZmlsZSB2YWxpZGF0b3IgY3J0
key.pem: cHJvZmlsZSB2YWxpZGF0b3Iga2V5
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: linkerd-sp-validator-webhook-config
labels:
linkerd.io/control-plane-component: sp-validator
linkerd.io/control-plane-ns: linkerd
webhooks:
- name: linkerd-sp-validator.linkerd.io
clientConfig:
service:
name: linkerd-sp-validator
namespace: linkerd
path: "/"
caBundle: cHJvZmlsZSB2YWxpZGF0b3IgY3J0
failurePolicy: Fail
rules:
- operations: [ "CREATE" , "UPDATE" ]
apiGroups: ["linkerd.io"]
apiVersions: ["v1alpha1"]
resources: ["serviceprofiles"]
sideEffects: None
---
###
### Tap RBAC
###
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-linkerd-tap
labels:
linkerd.io/control-plane-component: tap
linkerd.io/control-plane-ns: linkerd
rules:
- apiGroups: [""]
resources: ["pods", "services", "replicationcontrollers", "namespaces"]
verbs: ["list", "get", "watch"]
- apiGroups: ["extensions", "apps"]
resources: ["daemonsets", "deployments", "replicasets", "statefulsets"]
verbs: ["list", "get", "watch"]
- apiGroups: ["extensions", "batch"]
resources: ["jobs"]
verbs: ["list" , "get", "watch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-linkerd-tap
labels:
linkerd.io/control-plane-component: tap
linkerd.io/control-plane-ns: linkerd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: linkerd-linkerd-tap
subjects:
- kind: ServiceAccount
name: linkerd-tap
namespace: linkerd
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-tap
namespace: linkerd
labels:
linkerd.io/control-plane-component: tap
linkerd.io/control-plane-ns: linkerd
---
###
### Control Plane PSP
###
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: linkerd-linkerd-control-plane
labels:
linkerd.io/control-plane-ns: linkerd
spec:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
allowedCapabilities:
- NET_ADMIN
- NET_RAW
requiredDropCapabilities:
- ALL
hostNetwork: false
hostIPC: false
hostPID: false
seLinux:
rule: RunAsAny
runAsUser:
rule: RunAsAny
supplementalGroups:
rule: MustRunAs
ranges:
- min: 1
max: 65535
fsGroup:
rule: MustRunAs
ranges:
- min: 1
max: 65535
volumes:
- configMap
- emptyDir
- secret
- projected
- downwardAPI
- persistentVolumeClaim
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: linkerd-psp
namespace: linkerd
labels:
linkerd.io/control-plane-ns: linkerd
rules:
- apiGroups: ['policy', 'extensions']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- linkerd-linkerd-control-plane
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: linkerd-psp
namespace: linkerd
labels:
linkerd.io/control-plane-ns: linkerd
roleRef:
kind: Role
name: linkerd-psp
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: linkerd-controller
namespace: linkerd
- kind: ServiceAccount
name: linkerd-grafana
namespace: linkerd
- kind: ServiceAccount
name: linkerd-identity
namespace: linkerd
- kind: ServiceAccount
name: linkerd-prometheus
namespace: linkerd
- kind: ServiceAccount
name: linkerd-proxy-injector
namespace: linkerd
- kind: ServiceAccount
name: linkerd-sp-validator
namespace: linkerd
- kind: ServiceAccount
name: linkerd-tap
namespace: linkerd
- kind: ServiceAccount
name: linkerd-web
namespace: linkerd
---

View File

@ -56,6 +56,7 @@ func (options *upgradeOptions) upgradeOnlyFlagSet() *pflag.FlagSet {
// newCmdUpgradeConfig is a subcommand for `linkerd upgrade config`
func newCmdUpgradeConfig(options *upgradeOptions) *cobra.Command {
flags := options.recordableFlagSet()
cmd := &cobra.Command{
Use: "config [flags]",
Args: cobra.NoArgs,
@ -66,7 +67,7 @@ Note that this command should be followed by "linkerd upgrade control-plane".`,
Example: ` # Default upgrade.
linkerd upgrade config | kubectl apply -f -`,
RunE: func(cmd *cobra.Command, args []string) error {
return upgradeRunE(options, configStage, nil)
return upgradeRunE(options, configStage, flags)
},
}

View File

@ -30,11 +30,13 @@ func testUpgradeOptions() *upgradeOptions {
func TestRenderUpgrade(t *testing.T) {
testCases := []struct {
stage string
k8sConfigs []string
outputfile string
err error
}{
{
"",
[]string{`
kind: ConfigMap
apiVersion: v1
@ -71,6 +73,7 @@ data:
nil,
},
{
"",
[]string{`
kind: ConfigMap
apiVersion: v1
@ -107,6 +110,44 @@ data:
nil,
},
{
configStage,
[]string{`
kind: ConfigMap
apiVersion: v1
metadata:
name: linkerd-config
namespace: linkerd
labels:
linkerd.io/control-plane-component: controller
annotations:
linkerd.io/created-by: linkerd/cli edge-19.4.1
data:
global: |
{"linkerdNamespace":"linkerd","cniEnabled":false,"version":"edge-19.4.1","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBgzCCASmgAwIBAgIBATAKBggqhkjOPQQDAjApMScwJQYDVQQDEx5pZGVudGl0\neS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMTkwNDA0MjM1MzM3WhcNMjAwNDAz\nMjM1MzU3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9j\nYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT+Sb5X4wi4XP0X3rJwMp23VBdg\nEMMU8EU+KG8UI2LmC5Vjg5RWLOW6BJjBmjXViKM+b+1/oKAeOg6FrJk8qyFlo0Iw\nQDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC\nMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAKUFG3sYOS++bakW\nYmJZU45iCdTLtaelMDSFiHoC9eBKAiBDWzzo+/CYLLmn33bAEn8pQnogP4Fx06aj\n+U9K4WlbzA==\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s"}}
proxy: |
{"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd2_proxy=info"},"disableExternalProfiles":true}
install: |
{"uuid":"57af298c-58b0-43fc-8d88-3c338789bfbc","cliVersion":"edge-19.4.1","flags":[{"name":"ha","value":"true"}]}`,
`
kind: Secret
apiVersion: v1
metadata:
name: linkerd-identity-issuer
namespace: linkerd
labels:
linkerd.io/control-plane-component: identity
annotations:
linkerd.io/created-by: linkerd/cli edge-19.4.1
linkerd.io/identity-issuer-expiry: 2020-04-03T23:53:57Z
data:
crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJnekNDQVNtZ0F3SUJBZ0lCQVRBS0JnZ3Foa2pPUFFRREFqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDAKZVM1c2FXNXJaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dIaGNOTVRrd05EQTBNak0xTXpNM1doY05NakF3TkRBegpNak0xTXpVM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1clpYSmtMbU5zZFhOMFpYSXViRzlqCllXd3dXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVCtTYjVYNHdpNFhQMFgzckp3TXAyM1ZCZGcKRU1NVThFVStLRzhVSTJMbUM1VmpnNVJXTE9XNkJKakJtalhWaUtNK2IrMS9vS0FlT2c2RnJKazhxeUZsbzBJdwpRREFPQmdOVkhROEJBZjhFQkFNQ0FRWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DCk1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0NnWUlLb1pJemowRUF3SURTQUF3UlFJaEFLVUZHM3NZT1MrK2Jha1cKWW1KWlU0NWlDZFRMdGFlbE1EU0ZpSG9DOWVCS0FpQkRXenpvKy9DWUxMbW4zM2JBRW44cFFub2dQNEZ4MDZhagorVTlLNFdsYnpBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUhaaEFWTnNwSlRzMWZ4YmZ4VmptTTJvMTNTOFd4U2VVdTlrNFhZK0NPY3JvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFL2ttK1YrTUl1Rno5Rjk2eWNES2R0MVFYWUJEREZQQkZQaWh2RkNOaTVndVZZNE9VVml6bAp1Z1NZd1pvMTFZaWpQbS90ZjZDZ0hqb09oYXlaUEtzaFpRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=`,
},
"upgrade_ha_config.golden",
nil,
},
{
"",
[]string{},
"",
errors.New("could not fetch configs from kubernetes: configmaps \"linkerd-config\" not found"),
@ -124,7 +165,7 @@ data:
t.Fatalf("Error mocking k8s client: %s", err)
}
values, configs, err := options.validateAndBuild("", clientset, flags)
values, configs, err := options.validateAndBuild(tc.stage, clientset, flags)
if !reflect.DeepEqual(err, tc.err) {
t.Fatalf("Expected \"%s\", got \"%s\"", tc.err, err)
} else if err == nil {