// Copyright (c) 2021 Multus 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.
//

// this generates kubeconfig file for multus based on service account
package main

import (
	"encoding/base64"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
)

const userRWPermission = 0600

const (
	cniConfigDirVarName   = "cni-config-dir"
	k8sCAFilePathVarName  = "kube-ca-file"
	k8sServiceHostVarName = "k8s-service-host"
	k8sServicePortVarName = "k8s-service-port"
	serviceAccountPath    = "/var/run/secrets/kubernetes.io/serviceaccount"
	skipTLSVerifyVarName  = "skip-tls-verify"
)

const (
	defaultCniConfigDir   = "/host/etc/cni/net.d"
	defaultK8sCAFilePath  = ""
	defaultK8sServiceHost = ""
	defaultK8sServicePort = 0
	defaultSkipTLSValue   = false
)

func main() {
	k8sServiceHost := flag.String(k8sServiceHostVarName, defaultK8sServiceHost, "Cluster IP of the kubernetes service")
	k8sServicePort := flag.Int(k8sServicePortVarName, defaultK8sServicePort, "Port of the kubernetes service")
	skipTLSVerify := flag.Bool(skipTLSVerifyVarName, defaultSkipTLSValue, "Should TLS verification be skipped")
	kubeCAFilePath := flag.String(k8sCAFilePathVarName, defaultK8sCAFilePath, "Override the default kubernetes CA file path")
	cniConfigDir := flag.String(cniConfigDirVarName, defaultCniConfigDir, "CNI config dir")
	flag.Parse()

	if *k8sServiceHost == defaultK8sServiceHost {
		logInvalidArg("must provide the k8s service cluster port")
	}
	if *k8sServicePort == defaultK8sServicePort {
		logInvalidArg("must provide the k8s service cluster port")
	}
	if *kubeCAFilePath == defaultK8sServiceHost {
		*kubeCAFilePath = serviceAccountPath + "/ca.crt"
	}

	tlsCfg := "insecure-skip-tls-verify: true"
	if !*skipTLSVerify {
		kubeCAFileContents, err := k8sCAFileContentsBase64(*kubeCAFilePath)
		if err != nil {
			logError("failed grabbing CA file: %w", err)
		}
		tlsCfg = "certificate-authority-data: " + kubeCAFileContents
	}

	multusConfigDir := *cniConfigDir + "/multus.d/"
	if err := prepareCNIConfigDir(multusConfigDir); err != nil {
		logError("failed to create CNI config dir: %w", err)
	}
	kubeConfigFilePath := *cniConfigDir + "/multus.d/multus.kubeconfig"
	serviceAccountToken, err := k8sKubeConfigToken(serviceAccountPath + "/token")
	if err != nil {
		logError("failed grabbing k8s token: %w", err)
	}
	if err := writeKubeConfig(kubeConfigFilePath, "https", *k8sServiceHost, *k8sServicePort, tlsCfg, serviceAccountToken); err != nil {
		logError("failed generating kubeconfig: %w", err)
	}
}

func k8sCAFileContentsBase64(pathCAFile string) (string, error) {
	data, err := ioutil.ReadFile(pathCAFile)
	if err != nil {
		return "", fmt.Errorf("failed reading file %s: %w", pathCAFile, err)
	}
	return strings.Trim(base64.StdEncoding.EncodeToString(data), "\n"), nil
}

func k8sKubeConfigToken(tokenPath string) (string, error) {
	data, err := ioutil.ReadFile(tokenPath)
	if err != nil {
		return "", fmt.Errorf("failed reading file %s: %w", tokenPath, err)
	}
	return string(data), nil
}

func writeKubeConfig(outputPath string, protocol string, k8sServiceIP string, k8sServicePort int, tlsConfig string, serviceAccountToken string) error {
	kubeConfigTemplate := `
# Kubeconfig file for Multus CNI plugin.
apiVersion: v1
kind: Config
clusters:
- name: local
  cluster:
    server: %s://[%s]:%d
    %s
users:
- name: multus
  user:
    token: "%s"
contexts:
- name: multus-context
  context:
    cluster: local
    user: multus
current-context: multus-context
`
	kubeconfig := fmt.Sprintf(kubeConfigTemplate, protocol, k8sServiceIP, k8sServicePort, tlsConfig, serviceAccountToken)
	logInfo("Generated KubeConfig: \n%s", kubeconfig)
	return ioutil.WriteFile(outputPath, []byte(kubeconfig), userRWPermission)
}

func prepareCNIConfigDir(cniConfigDirPath string) error {
	return os.MkdirAll(cniConfigDirPath, userRWPermission)
}

func logInvalidArg(format string, values ...interface{}) {
	log.Printf("ERROR: %s", fmt.Errorf(format, values...).Error())
	flag.PrintDefaults()
	os.Exit(1)
}

func logError(format string, values ...interface{}) {
	log.Printf("ERROR: %s", fmt.Errorf(format, values...).Error())
	os.Exit(1)
}

func logInfo(format string, values ...interface{}) {
	log.Printf("INFO: %s", fmt.Sprintf(format, values...))
}
