Address comments in review

- start cleaning up `cmd/manual.go`
- refine progress and error messages
- add missing blank lines after the license headers
- run `gofmt -s -w`
- do not set fake cloud provider
- add a note on why we cannot remove `HostNetwork: true` from `kube-discovery` pod just yet
- taint master and use `role=master`, set tolerations and affinity for `kube-discovery`
- parametrise log-level flag for all components
pull/6/head
Ilya Dmitrichenko 2016-09-02 11:19:38 +02:00
parent a82c490315
commit 1c132fe974
No known key found for this signature in database
GPG Key ID: E7889175A6C0CEB9
13 changed files with 220 additions and 98 deletions

View File

@ -20,6 +20,7 @@ import (
"fmt"
"os"
"path"
"runtime"
"strings"
"github.com/spf13/pflag"
@ -40,13 +41,16 @@ func getEnvParams() map[string]string {
}
envParams := map[string]string{
"prefix": globalPrefix,
"host_pki_path": path.Join(globalPrefix, "pki"),
"hyperkube_image": "gcr.io/google_containers/hyperkube:v1.4.0-alpha.3",
"discovery_image": "dgoodwin/kubediscovery:latest",
"prefix": globalPrefix,
"host_pki_path": path.Join(globalPrefix, "pki"),
// TODO find a way to specify image versions for all of these...
"hyperkube_image": fmt.Sprintf("gcr.io/google_containers/hyperkube-%s:%s", runtime.GOARCH, "v1.4.0-alpha.3"),
"discovery_image": "dgoodwin/kubediscovery:latest",
"etcd_image": fmt.Sprintf("gcr.io/google_containers/etcd-%s:%s", runtime.GOARCH, "2.2.5"),
"component_loglevel": "--v=4",
}
for k, _ := range envParams {
for k := range envParams {
if v := os.Getenv(fmt.Sprintf("KUBE_%s", strings.ToUpper(k))); v != "" {
envParams[k] = v
}

View File

@ -17,8 +17,7 @@ limitations under the License.
package kubeadmapi
type BootstrapParams struct {
// A struct with methods that implement Discover()
// kubeadm will do the CSR dance
// TODO this is mostly out of date and bloated now, let's revisit this soon
Discovery *OutOfBandDiscovery
EnvParams map[string]string
}

View File

@ -91,6 +91,10 @@ func RunInit(out io.Writer, cmd *cobra.Command, args []string, params *kubeadmap
return err
}
if err := kubemaster.UpdateMasterRoleLabelsAndTaints(client); err != nil {
return err
}
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(params, client, caCert); err != nil {
return err
}

View File

@ -27,12 +27,12 @@ import (
kubemaster "k8s.io/kubernetes/pkg/kubeadm/master"
kubenode "k8s.io/kubernetes/pkg/kubeadm/node"
kubeadmutil "k8s.io/kubernetes/pkg/kubeadm/util"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
netutil "k8s.io/kubernetes/pkg/util/net"
// TODO: cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
manual_done_msgf = dedent.Dedent(`
manual_init_done_msgf = dedent.Dedent(`
Master initialization complete:
* Static pods written and kubelet's kubeconfig written.
@ -48,6 +48,14 @@ var (
kubeadm manual bootstrap join-node --ca-cert-file <path-to-ca-cert> \
--token %s --api-server-urls https://%s:443/
`)
manual_join_done_msgf = dedent.Dedent(`
Node join complete:
* Certificate signing request sent to master and response
received.
* Kubelet informed of new secure connection details.
Run 'kubectl get nodes' on the master to see this node join.
`)
)
// TODO --token here becomes Discovery.BearerToken and not Discovery.GivenToken
@ -92,42 +100,9 @@ func NewCmdManualBootstrapInitMaster(out io.Writer, params *kubeadmapi.Bootstrap
Will create TLS certificates and set up static pods for Kubernetes master
components.
`),
RunE: func(cmd *cobra.Command, args []string) error {
if params.Discovery.ListenIP == "" {
ip, err := netutil.ChooseHostInterface()
if err != nil {
return fmt.Errorf("Unable to autodetect IP address [%s], please specify with --listen-ip", err)
}
params.Discovery.ListenIP = ip.String()
}
if err := kubemaster.CreateTokenAuthFile(params); err != nil {
return err
}
if err := kubemaster.WriteStaticPodManifests(params); err != nil {
return err
}
caKey, caCert, err := kubemaster.CreatePKIAssets(params)
if err != nil {
return err
}
kubeconfigs, err := kubemaster.CreateCertsAndConfigForClients(params, []string{"kubelet", "admin"}, caKey, caCert)
if err != nil {
return err
}
for name, kubeconfig := range kubeconfigs {
if err := kubeadmutil.WriteKubeconfigIfNotExists(params, name, kubeconfig); err != nil {
out.Write([]byte(fmt.Sprintf("Unable to write admin for master:\n%s\n", err)))
return nil
}
}
// TODO use templates to reference struct fields directly as order of args is fragile
fmt.Fprintf(out, manual_done_msgf,
params.Discovery.BearerToken,
params.Discovery.ListenIP,
)
return nil
Run: func(cmd *cobra.Command, args []string) {
err := RunManualBootstrapInitMaster(out, cmd, args, params)
cmdutil.CheckErr(err)
},
}
@ -143,43 +118,50 @@ func NewCmdManualBootstrapInitMaster(out io.Writer, params *kubeadmapi.Bootstrap
return cmd
}
func RunManualBootstrapInitMaster(out io.Writer, cmd *cobra.Command, args []string, params *kubeadmapi.BootstrapParams) error {
if params.Discovery.ListenIP == "" {
ip, err := netutil.ChooseHostInterface()
if err != nil {
return fmt.Errorf("<cmd/join> unable to autodetect IP address [%s], please specify with --listen-ip", err)
}
params.Discovery.ListenIP = ip.String()
}
if err := kubemaster.CreateTokenAuthFile(params); err != nil {
return err
}
if err := kubemaster.WriteStaticPodManifests(params); err != nil {
return err
}
caKey, caCert, err := kubemaster.CreatePKIAssets(params)
if err != nil {
return err
}
kubeconfigs, err := kubemaster.CreateCertsAndConfigForClients(params, []string{"kubelet", "admin"}, caKey, caCert)
if err != nil {
return err
}
for name, kubeconfig := range kubeconfigs {
if err := kubeadmutil.WriteKubeconfigIfNotExists(params, name, kubeconfig); err != nil {
return err
}
}
// TODO use templates to reference struct fields directly as order of args is fragile
fmt.Fprintf(out, manual_init_done_msgf,
params.Discovery.BearerToken,
params.Discovery.ListenIP,
)
return nil
}
func NewCmdManualBootstrapJoinNode(out io.Writer, params *kubeadmapi.BootstrapParams) *cobra.Command {
cmd := &cobra.Command{
Use: "join-node",
Short: "Manually bootstrap a node 'out-of-band', joining it into a cluster with extant control plane",
Run: func(cmd *cobra.Command, args []string) {
if params.Discovery.CaCertFile == "" {
out.Write([]byte(fmt.Sprintf("Must specify --ca-cert-file (see --help)\n")))
return
}
if params.Discovery.ApiServerURLs == "" {
out.Write([]byte(fmt.Sprintf("Must specify --api-server-urls (see --help)\n")))
return
}
kubeconfig, err := kubenode.PerformTLSBootstrapFromParams(params)
if err != nil {
out.Write([]byte(fmt.Sprintf("Failed to perform TLS bootstrap: %s\n", err)))
return
}
//fmt.Println("recieved signed certificate from the API server, will write `/etc/kubernetes/kubelet.conf`...")
err = kubeadmutil.WriteKubeconfigIfNotExists(params, "kubelet", kubeconfig)
if err != nil {
out.Write([]byte(fmt.Sprintf("Unable to write config for node:\n%s\n", err)))
return
}
out.Write([]byte(dedent.Dedent(`
Node join complete:
* Certificate signing request sent to master and response
received.
* Kubelet informed of new secure connection details.
Run 'kubectl get nodes' on the master to see this node join.
`)))
err := RunManualBootstrapJoinNode(out, cmd, args, params)
cmdutil.CheckErr(err)
},
}
cmd.PersistentFlags().StringVarP(&params.Discovery.CaCertFile, "ca-cert-file", "", "",
@ -193,3 +175,30 @@ func NewCmdManualBootstrapJoinNode(out io.Writer, params *kubeadmapi.BootstrapPa
return cmd
}
func RunManualBootstrapJoinNode(out io.Writer, cmd *cobra.Command, args []string, params *kubeadmapi.BootstrapParams) error {
if params.Discovery.CaCertFile == "" {
fmt.Fprintf(out, "Must specify --ca-cert-file (see --help)\n")
return nil
}
if params.Discovery.ApiServerURLs == "" {
fmt.Fprintf(out, "Must specify --api-server-urls (see --help)\n")
return nil
}
kubeconfig, err := kubenode.PerformTLSBootstrapFromParams(params)
if err != nil {
fmt.Fprintf(out, "Failed to perform TLS bootstrap: %s\n", err)
return err
}
err = kubeadmutil.WriteKubeconfigIfNotExists(params, "kubelet", kubeconfig)
if err != nil {
fmt.Fprintf(out, "Unable to write config for node:\n%s\n", err)
return err
}
fmt.Fprintf(out, manual_join_done_msgf)
return nil
}

View File

@ -13,6 +13,7 @@ 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 kubemaster
import (
@ -35,7 +36,7 @@ func createKubeProxyPodSpec(params *kubeadmapi.BootstrapParams) api.PodSpec {
"/hyperkube",
"proxy",
"--kubeconfig=/run/kubeconfig",
COMPONENT_LOGLEVEL,
params.EnvParams["component_loglevel"],
},
SecurityContext: &api.SecurityContext{Privileged: &privilegedTrue},
VolumeMounts: []api.VolumeMount{
@ -80,6 +81,7 @@ func createKubeProxyPodSpec(params *kubeadmapi.BootstrapParams) api.PodSpec {
func CreateEssentialAddons(params *kubeadmapi.BootstrapParams, client *clientset.Clientset) error {
kubeProxyDaemonSet := NewDaemonSet("kube-proxy", createKubeProxyPodSpec(params))
SetMasterTaintTolerations(&kubeProxyDaemonSet.Spec.Template.ObjectMeta)
if _, err := client.Extensions().DaemonSets(api.NamespaceSystem).Create(kubeProxyDaemonSet); err != nil {
return fmt.Errorf("<master/addons> failed creating essential kube-proxy addon [%s]", err)

View File

@ -17,10 +17,12 @@ limitations under the License.
package kubemaster
import (
"encoding/json"
"fmt"
"time"
"k8s.io/kubernetes/pkg/api"
apierrs "k8s.io/kubernetes/pkg/api/errors"
unversionedapi "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
@ -53,6 +55,7 @@ func CreateClientAndWaitForAPI(adminConfig *clientcmdapi.Config) (*clientset.Cli
if err != nil {
return false, nil
}
// TODO revisit this when we implement HA
if len(cs.Items) < 3 {
fmt.Println("<master/apiclient> not all control plane components are ready yet")
return false, nil
@ -66,11 +69,32 @@ func CreateClientAndWaitForAPI(adminConfig *clientcmdapi.Config) (*clientset.Cli
}
}
fmt.Printf("<master/apiclient> all control plane components are healthy after %s seconds\n", time.Since(start).Seconds())
fmt.Printf("<master/apiclient> all control plane components are healthy after %f seconds\n", time.Since(start).Seconds())
return true, nil
})
fmt.Println("<master/apiclient> waiting for at least one node to register and become ready")
start = time.Now()
wait.PollInfinite(500*time.Millisecond, func() (bool, error) {
nodeList, err := client.Nodes().List(api.ListOptions{})
if err != nil {
fmt.Println("<master/apiclient> temporarily unable to list nodes (will retry)")
return false, nil
}
if len(nodeList.Items) < 1 {
//fmt.Printf("<master/apiclient> %d nodes have registered so far", len(nodeList.Items))
return false, nil
}
n := &nodeList.Items[0]
if !api.IsNodeReady(n) {
fmt.Println("<master/apiclient> first node has registered, but is not ready yet")
return false, nil
}
fmt.Printf("<master/apiclient> first node is ready after %f seconds\n", time.Since(start).Seconds())
return true, nil
})
// TODO may be also check node status
return client, nil
}
@ -103,9 +127,72 @@ func NewDeployment(deploymentName string, replicas int32, podSpec api.PodSpec) *
}
}
func TaintMaster(*clientset.Clientset) error {
// TODO
annotations := make(map[string]string)
annotations[api.TaintsAnnotationKey] = ""
// It's safe to do this for alpha, as we don't have HA and there is no way we can get
// more then one node here (TODO find a way to determine owr own node name)
func findMyself(client *clientset.Clientset) (*api.Node, error) {
nodeList, err := client.Nodes().List(api.ListOptions{})
if err != nil {
return nil, fmt.Errorf("unable to list nodes [%s]", err)
}
if len(nodeList.Items) < 1 {
return nil, fmt.Errorf("no nodes found")
}
node := &nodeList.Items[0]
return node, nil
}
func attemptToUpdateMasterRoleLabelsAndTaints(client *clientset.Clientset) error {
n, err := findMyself(client)
if err != nil {
return err
}
n.ObjectMeta.Labels["kubeadm.alpha.kubernetes.io/role"] = "master"
taintsAnnotation, _ := json.Marshal([]api.Taint{{Key: "dedicated", Value: "master", Effect: "NoSchedule"}})
n.ObjectMeta.Annotations[api.TaintsAnnotationKey] = string(taintsAnnotation)
if _, err := client.Nodes().Update(n); err != nil {
if apierrs.IsConflict(err) {
fmt.Println("<master/apiclient> temporarily unable to update master node metadata due to conflict (will retry)")
time.Sleep(500 * time.Millisecond)
attemptToUpdateMasterRoleLabelsAndTaints(client)
} else {
return err
}
}
return nil
}
func UpdateMasterRoleLabelsAndTaints(client *clientset.Clientset) error {
err := attemptToUpdateMasterRoleLabelsAndTaints(client)
if err != nil {
return fmt.Errorf("<master/apiclient> failed to update master node - %s", err)
}
return nil
}
func SetMasterTaintTolerations(meta *api.ObjectMeta) {
tolerationsAnnotation, _ := json.Marshal([]api.Toleration{{Key: "dedicated", Value: "master", Effect: "NoSchedule"}})
if meta.Annotations == nil {
meta.Annotations = map[string]string{}
}
meta.Annotations[api.TolerationsAnnotationKey] = string(tolerationsAnnotation)
}
func SetMasterNodeAffinity(meta *api.ObjectMeta) {
nodeAffinity := &api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{{
MatchExpressions: []api.NodeSelectorRequirement{{
Key: "kubeadm.alpha.kubernetes.io/role", Operator: api.NodeSelectorOpIn, Values: []string{"master"},
}},
}},
},
}
affinityAnnotation, _ := json.Marshal(api.Affinity{NodeAffinity: nodeAffinity})
if meta.Annotations == nil {
meta.Annotations = map[string]string{}
}
meta.Annotations[api.AffinityAnnotationKey] = string(affinityAnnotation)
}

View File

@ -13,6 +13,7 @@ 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 kubemaster
import (
@ -61,7 +62,11 @@ func encodeKubeDiscoverySecretData(params *kubeadmapi.BootstrapParams, caCert *x
func newKubeDiscoveryPodSpec(params *kubeadmapi.BootstrapParams) api.PodSpec {
return api.PodSpec{
SecurityContext: &api.PodSecurityContext{HostNetwork: true}, // TODO we should just use map it to a host port
// We have to use host network namespace, as `HostPort`/`HostIP` are Docker's
// buisness and CNI support isn't quite there yet (except for kubenet)
// (see https://github.com/kubernetes/kubernetes/issues/31307)
// TODO update this when #31307 is resolved
SecurityContext: &api.PodSecurityContext{HostNetwork: true},
Containers: []api.Container{{
Name: kubeDiscoverynName,
Image: params.EnvParams["discovery_image"],
@ -71,6 +76,10 @@ func newKubeDiscoveryPodSpec(params *kubeadmapi.BootstrapParams) api.PodSpec {
MountPath: "/tmp/secret", // TODO use a shared constant
ReadOnly: true,
}},
Ports: []api.ContainerPort{
// TODO when CNI issue (#31307) is resolved, we should add `HostIP: params.Discovery.ListenIP`
{Name: "http", ContainerPort: 9898, HostPort: 9898},
},
}},
Volumes: []api.Volume{{
Name: kubeDiscoverySecretName,
@ -82,8 +91,7 @@ func newKubeDiscoveryPodSpec(params *kubeadmapi.BootstrapParams) api.PodSpec {
}
func newKubeDiscovery(params *kubeadmapi.BootstrapParams, caCert *x509.Certificate) kubeDiscovery {
// TODO pin to master
return kubeDiscovery{
kd := kubeDiscovery{
Deployment: NewDeployment(kubeDiscoverynName, 1, newKubeDiscoveryPodSpec(params)),
Secret: &api.Secret{
ObjectMeta: api.ObjectMeta{Name: kubeDiscoverySecretName},
@ -91,16 +99,21 @@ func newKubeDiscovery(params *kubeadmapi.BootstrapParams, caCert *x509.Certifica
Data: encodeKubeDiscoverySecretData(params, caCert),
},
}
SetMasterTaintTolerations(&kd.Deployment.Spec.Template.ObjectMeta)
SetMasterNodeAffinity(&kd.Deployment.Spec.Template.ObjectMeta)
return kd
}
func CreateDiscoveryDeploymentAndSecret(params *kubeadmapi.BootstrapParams, client *clientset.Clientset, caCert *x509.Certificate) error {
kd := newKubeDiscovery(params, caCert)
if _, err := client.Extensions().Deployments(api.NamespaceSystem).Create(kd.Deployment); err != nil {
return fmt.Errorf("<master/discovery> failed to create %q deployment", kubeDiscoverynName)
return fmt.Errorf("<master/discovery> failed to create %q deployment [%s]", kubeDiscoverynName, err)
}
if _, err := client.Secrets(api.NamespaceSystem).Create(kd.Secret); err != nil {
return fmt.Errorf("<master/discovery> failed to create %q secret", kubeDiscoverySecretName)
return fmt.Errorf("<master/discovery> failed to create %q secret [%s]", kubeDiscoverySecretName, err)
}
fmt.Println("<master/discovery> created essential addon: kube-discovery")

View File

@ -35,7 +35,6 @@ import (
// init master` and `kubeadm manual bootstrap master` can get going.
const (
COMPONENT_LOGLEVEL = "--v=4"
SERVICE_CLUSTER_IP_RANGE = "--service-cluster-ip-range=10.16.0.0/12"
CLUSTER_NAME = "--cluster-name=kubernetes"
MASTER = "--master=127.0.0.1:8080"
@ -55,7 +54,7 @@ func WriteStaticPodManifests(params *kubeadmapi.BootstrapParams) error {
"--advertise-client-urls=http://127.0.0.1:2379,http://127.0.0.1:4001",
"--data-dir=/var/etcd/data",
},
Image: "gcr.io/google_containers/etcd:2.2.1", // TODO parametrise
Image: params.EnvParams["etcd_image"],
LivenessProbe: componentProbe(2379, "/health"),
Name: "etcd-server",
Resources: componentResources("200m"),
@ -69,7 +68,6 @@ func WriteStaticPodManifests(params *kubeadmapi.BootstrapParams) error {
"apiserver",
"--address=127.0.0.1",
"--etcd-servers=http://127.0.0.1:2379",
"--cloud-provider=fake", // TODO parametrise
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
SERVICE_CLUSTER_IP_RANGE,
"--service-account-key-file=/etc/kubernetes/pki/apiserver-key.pem",
@ -78,7 +76,7 @@ func WriteStaticPodManifests(params *kubeadmapi.BootstrapParams) error {
"--tls-private-key-file=/etc/kubernetes/pki/apiserver-key.pem",
"--secure-port=443",
"--allow-privileged",
COMPONENT_LOGLEVEL,
params.EnvParams["component_loglevel"],
"--token-auth-file=/etc/kubernetes/pki/tokens.csv",
},
VolumeMounts: []api.VolumeMount{pkiVolumeMount()},
@ -99,7 +97,7 @@ func WriteStaticPodManifests(params *kubeadmapi.BootstrapParams) error {
"--cluster-signing-cert-file=/etc/kubernetes/pki/ca.pem",
"--cluster-signing-key-file=/etc/kubernetes/pki/ca-key.pem",
"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap",
COMPONENT_LOGLEVEL,
params.EnvParams["component_loglevel"],
},
VolumeMounts: []api.VolumeMount{pkiVolumeMount()},
LivenessProbe: componentProbe(10252, "/healthz"),
@ -113,7 +111,7 @@ func WriteStaticPodManifests(params *kubeadmapi.BootstrapParams) error {
"scheduler",
"--leader-elect",
MASTER,
COMPONENT_LOGLEVEL,
params.EnvParams["component_loglevel"],
},
LivenessProbe: componentProbe(10251, "/healthz"),
Resources: componentResources("100m"),

View File

@ -175,6 +175,6 @@ func CreatePKIAssets(params *kubeadmapi.BootstrapParams) (*rsa.PrivateKey, *x509
}
// TODO print a summary of SANs used and checksums (signatures) of each of the certiicates
fmt.Println("<master/pki> created keys and certificates in %q", params.EnvParams["host_pki_path"])
fmt.Printf("<master/pki> created keys and certificates in %q\n", params.EnvParams["host_pki_path"])
return caKey, caCert, nil
}

View File

@ -13,6 +13,7 @@ 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 kubemaster
import (
@ -38,6 +39,8 @@ func generateTokenIfNeeded(params *kubeadmapi.BootstrapParams) error {
return err
}
fmt.Printf("<master/tokens> generated token: %q\n", params.Discovery.GivenToken)
} else {
fmt.Println("<master/tokens> accepted provided token")
}
return nil

View File

@ -13,6 +13,7 @@ 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 kubenode
import (
@ -43,7 +44,7 @@ func RetrieveTrustedClusterInfo(params *kubeadmapi.BootstrapParams) (*clientcmda
return nil, fmt.Errorf("<node/discovery> failed to consturct an HTTP request [%s]", err)
}
fmt.Println("<node/discovery> created cluster info discovery client, requesting info from %q", requestURL)
fmt.Printf("<node/discovery> created cluster info discovery client, requesting info from %q\n", requestURL)
res, err := http.DefaultClient.Do(req)
if err != nil {

View File

@ -98,6 +98,6 @@ func WriteKubeconfigIfNotExists(params *kubeadmapi.BootstrapParams, name string,
return fmt.Errorf("<util/kubeconfig> failed to write to %q [%s]", filename, err)
}
fmt.Println("<util/kubeconfig> created %q", filename)
fmt.Printf("<util/kubeconfig> created %q\n", filename)
return nil
}

View File

@ -13,6 +13,7 @@ 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 kubeadmutil
import (
@ -61,8 +62,9 @@ func GenerateToken(params *kubeadmapi.BootstrapParams) error {
func UseGivenTokenIfValid(params *kubeadmapi.BootstrapParams) (bool, error) {
if params.Discovery.GivenToken == "" {
return false, nil
return false, nil // not given
}
fmt.Println("<util/tokens> validating provided token")
givenToken := strings.Split(strings.ToLower(params.Discovery.GivenToken), ".")
// TODO print desired format
// TODO could also print more specific messages in each case