<commit_before>package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"time"

	"github.com/Azure/acs-engine/pkg/acsengine"
	"github.com/Azure/acs-engine/pkg/api"
	"github.com/Azure/acs-engine/pkg/armhelpers"
	"github.com/Azure/acs-engine/pkg/helpers"
	"github.com/Azure/acs-engine/pkg/i18n"
	"github.com/Azure/acs-engine/pkg/operations/kubernetesupgrade"
	"github.com/leonelquinteros/gotext"

	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

const (
	upgradeName             = "upgrade"
	upgradeShortDescription = "Upgrade an existing Kubernetes cluster"
	upgradeLongDescription  = "Upgrade an existing Kubernetes cluster, one minor version at a time"
)

type upgradeCmd struct {
	authArgs

	// user input
	resourceGroupName   string
	deploymentDirectory string
	upgradeVersion      string
	location            string
	timeoutInMinutes    int

	// derived
	containerService    *api.ContainerService
	apiVersion          string
	client              armhelpers.ACSEngineClient
	locale              *gotext.Locale
	nameSuffix          string
	agentPoolsToUpgrade []string
	timeout             *time.Duration
}

// NewUpgradeCmd run a command to upgrade a Kubernetes cluster
func newUpgradeCmd() *cobra.Command {
	uc := upgradeCmd{}

	upgradeCmd := &cobra.Command{
		Use:   upgradeName,
		Short: upgradeShortDescription,
		Long:  upgradeLongDescription,
		RunE: func(cmd *cobra.Command, args []string) error {
			return uc.run(cmd, args)
		},
	}

	f := upgradeCmd.Flags()
	f.StringVarP(&uc.location, "location", "l", "", "location the cluster is deployed in (required)")
	f.StringVarP(&uc.resourceGroupName, "resource-group", "g", "", "the resource group where the cluster is deployed (required)")
	f.StringVar(&uc.deploymentDirectory, "deployment-dir", "", "the location of the output from `generate` (required)")
	f.StringVar(&uc.upgradeVersion, "upgrade-version", "", "desired kubernetes version (required)")
	f.IntVar(&uc.timeoutInMinutes, "vm-timeout", -1, "how long to wait for each vm to be upgraded in minutes")
	addAuthFlags(&uc.authArgs, f)

	return upgradeCmd
}

func (uc *upgradeCmd) validate(cmd *cobra.Command) error {
	log.Infoln("validating...")

	var err error

	uc.locale, err = i18n.LoadTranslations()
	if err != nil {
		return fmt.Errorf("error loading translation files: %s", err.Error())
	}

	if uc.resourceGroupName == "" {
		cmd.Usage()
		return fmt.Errorf("--resource-group must be specified")
	}

	if uc.location == "" {
		cmd.Usage()
		return fmt.Errorf("--location must be specified")
	}
	uc.location = helpers.NormalizeAzureRegion(uc.location)

	if uc.timeoutInMinutes != -1 {
		timeout := time.Duration(uc.timeoutInMinutes) * time.Minute
		uc.timeout = &timeout
	}

	// TODO(colemick): add in the cmd annotation to help enable autocompletion
	if uc.upgradeVersion == "" {
		cmd.Usage()
		return fmt.Errorf("--upgrade-version must be specified")
	}

	if uc.deploymentDirectory == "" {
		cmd.Usage()
		return fmt.Errorf("--deployment-dir must be specified")
	}

	if err = uc.authArgs.validateAuthArgs(); err != nil {
		return fmt.Errorf("%s", err)
	}
	return nil
}

func (uc *upgradeCmd) loadCluster(cmd *cobra.Command) error {
	var err error
	_, err = uc.client.EnsureResourceGroup(uc.resourceGroupName, uc.location, nil)
	if err != nil {
		return fmt.Errorf("Error ensuring resource group: %s", err)
	}

	// load apimodel from the deployment directory
	apiModelPath := path.Join(uc.deploymentDirectory, "apimodel.json")

	if _, err = os.Stat(apiModelPath); os.IsNotExist(err) {
		return fmt.Errorf("specified api model does not exist (%s)", apiModelPath)
	}

	apiloader := &api.Apiloader{
		Translator: &i18n.Translator{
			Locale: uc.locale,
		},
	}
	uc.containerService, uc.apiVersion, err = apiloader.LoadContainerServiceFromFile(apiModelPath, true, true, nil)
	if err != nil {
		return fmt.Errorf("error parsing the api model: %s", err.Error())
	}

	if uc.containerService.Location == "" {
		uc.containerService.Location = uc.location
	} else if uc.containerService.Location != uc.location {
		return fmt.Errorf("--location does not match api model location")
	}

	// get available upgrades for container service
	orchestratorInfo, err := api.GetOrchestratorVersionProfile(uc.containerService.Properties.OrchestratorProfile)
	if err != nil {
		return fmt.Errorf("error getting list of available upgrades: %s", err.Error())
	}
	// add the current version if upgrade has failed
	orchestratorInfo.Upgrades = append(orchestratorInfo.Upgrades, &api.OrchestratorProfile{
		OrchestratorType:    uc.containerService.Properties.OrchestratorProfile.OrchestratorType,
		OrchestratorVersion: uc.containerService.Properties.OrchestratorProfile.OrchestratorVersion})

	// validate desired upgrade version and set goal state
	found := false
	for _, up := range orchestratorInfo.Upgrades {
		if up.OrchestratorVersion == uc.upgradeVersion {
			uc.containerService.Properties.OrchestratorProfile.OrchestratorVersion = uc.upgradeVersion
			found = true
			break
		}
	}
	if !found {
		return fmt.Errorf("version %s is not supported", uc.upgradeVersion)
	}

	if uc.client, err = uc.authArgs.getClient(); err != nil {
		return fmt.Errorf("Failed to get client: %s", err)
	}

	// Read name suffix to identify nodes in the resource group that belong
	// to this cluster.
	// TODO: Also update to read  namesuffix from the parameters file as
	// user could have specified a name suffix instead of using the default
	// value generated by ACS Engine
	templatePath := path.Join(uc.deploymentDirectory, "azuredeploy.json")
	contents, _ := ioutil.ReadFile(templatePath)

	var template interface{}
	json.Unmarshal(contents, &template)

	templateMap := template.(map[string]interface{})
	templateParameters := templateMap["parameters"].(map[string]interface{})

	nameSuffixParam := templateParameters["nameSuffix"].(map[string]interface{})
	uc.nameSuffix = nameSuffixParam["defaultValue"].(string)
	log.Infoln(fmt.Sprintf("Name suffix: %s", uc.nameSuffix))

	uc.agentPoolsToUpgrade = []string{}
	log.Infoln(fmt.Sprintf("Gathering agent pool names..."))
	for _, agentPool := range uc.containerService.Properties.AgentPoolProfiles {
		uc.agentPoolsToUpgrade = append(uc.agentPoolsToUpgrade, agentPool.Name)
	}
	return nil
}

func (uc *upgradeCmd) run(cmd *cobra.Command, args []string) error {
	err := uc.validate(cmd)
	if err != nil {
		log.Fatalf("error validating upgrade command: %v", err)
	}

	err = uc.loadCluster(cmd)
	if err != nil {
		log.Fatalf("error loading existing cluster: %v", err)
	}

	upgradeCluster := kubernetesupgrade.UpgradeCluster{
		Translator: &i18n.Translator{
			Locale: uc.locale,
		},
		Logger:      log.NewEntry(log.New()),
		Client:      uc.client,
		StepTimeout: uc.timeout,
	}

	kubeConfig, err := acsengine.GenerateKubeConfig(uc.containerService.Properties, uc.location)
	if err != nil {
		log.Fatalf("failed to generate kube config: %v", err) // TODO: cleanup
	}

	if err = upgradeCluster.UpgradeCluster(uc.authArgs.SubscriptionID, kubeConfig, uc.resourceGroupName,
		uc.containerService, uc.nameSuffix, uc.agentPoolsToUpgrade, BuildTag); err != nil {
		log.Fatalf("Error upgrading cluster: %v\n", err)
	}

	apiloader := &api.Apiloader{
		Translator: &i18n.Translator{
			Locale: uc.locale,
		},
	}
	b, err := apiloader.SerializeContainerService(uc.containerService, uc.apiVersion)
	if err != nil {
		return err
	}

	f := acsengine.FileSaver{
		Translator: &i18n.Translator{
			Locale: uc.locale,
		},
	}

	return f.SaveFile(uc.deploymentDirectory, "apimodel.json", b)
}
<commit_msg>Fix broken upgrade nil pointer dereference (#2879)<commit_after>package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"time"

	"github.com/Azure/acs-engine/pkg/acsengine"
	"github.com/Azure/acs-engine/pkg/api"
	"github.com/Azure/acs-engine/pkg/armhelpers"
	"github.com/Azure/acs-engine/pkg/helpers"
	"github.com/Azure/acs-engine/pkg/i18n"
	"github.com/Azure/acs-engine/pkg/operations/kubernetesupgrade"
	"github.com/leonelquinteros/gotext"

	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

const (
	upgradeName             = "upgrade"
	upgradeShortDescription = "Upgrade an existing Kubernetes cluster"
	upgradeLongDescription  = "Upgrade an existing Kubernetes cluster, one minor version at a time"
)

type upgradeCmd struct {
	authArgs

	// user input
	resourceGroupName   string
	deploymentDirectory string
	upgradeVersion      string
	location            string
	timeoutInMinutes    int

	// derived
	containerService    *api.ContainerService
	apiVersion          string
	client              armhelpers.ACSEngineClient
	locale              *gotext.Locale
	nameSuffix          string
	agentPoolsToUpgrade []string
	timeout             *time.Duration
}

// NewUpgradeCmd run a command to upgrade a Kubernetes cluster
func newUpgradeCmd() *cobra.Command {
	uc := upgradeCmd{}

	upgradeCmd := &cobra.Command{
		Use:   upgradeName,
		Short: upgradeShortDescription,
		Long:  upgradeLongDescription,
		RunE: func(cmd *cobra.Command, args []string) error {
			return uc.run(cmd, args)
		},
	}

	f := upgradeCmd.Flags()
	f.StringVarP(&uc.location, "location", "l", "", "location the cluster is deployed in (required)")
	f.StringVarP(&uc.resourceGroupName, "resource-group", "g", "", "the resource group where the cluster is deployed (required)")
	f.StringVar(&uc.deploymentDirectory, "deployment-dir", "", "the location of the output from `generate` (required)")
	f.StringVar(&uc.upgradeVersion, "upgrade-version", "", "desired kubernetes version (required)")
	f.IntVar(&uc.timeoutInMinutes, "vm-timeout", -1, "how long to wait for each vm to be upgraded in minutes")
	addAuthFlags(&uc.authArgs, f)

	return upgradeCmd
}

func (uc *upgradeCmd) validate(cmd *cobra.Command) error {
	log.Infoln("validating...")

	var err error

	uc.locale, err = i18n.LoadTranslations()
	if err != nil {
		return fmt.Errorf("error loading translation files: %s", err.Error())
	}

	if uc.resourceGroupName == "" {
		cmd.Usage()
		return fmt.Errorf("--resource-group must be specified")
	}

	if uc.location == "" {
		cmd.Usage()
		return fmt.Errorf("--location must be specified")
	}
	uc.location = helpers.NormalizeAzureRegion(uc.location)

	if uc.timeoutInMinutes != -1 {
		timeout := time.Duration(uc.timeoutInMinutes) * time.Minute
		uc.timeout = &timeout
	}

	// TODO(colemick): add in the cmd annotation to help enable autocompletion
	if uc.upgradeVersion == "" {
		cmd.Usage()
		return fmt.Errorf("--upgrade-version must be specified")
	}

	if uc.deploymentDirectory == "" {
		cmd.Usage()
		return fmt.Errorf("--deployment-dir must be specified")
	}

	if err = uc.authArgs.validateAuthArgs(); err != nil {
		return fmt.Errorf("%s", err)
	}
	return nil
}

func (uc *upgradeCmd) loadCluster(cmd *cobra.Command) error {
	var err error

	if uc.client, err = uc.authArgs.getClient(); err != nil {
		return fmt.Errorf("Failed to get client: %s", err)
	}

	_, err = uc.client.EnsureResourceGroup(uc.resourceGroupName, uc.location, nil)
	if err != nil {
		return fmt.Errorf("Error ensuring resource group: %s", err)
	}

	// load apimodel from the deployment directory
	apiModelPath := path.Join(uc.deploymentDirectory, "apimodel.json")

	if _, err = os.Stat(apiModelPath); os.IsNotExist(err) {
		return fmt.Errorf("specified api model does not exist (%s)", apiModelPath)
	}

	apiloader := &api.Apiloader{
		Translator: &i18n.Translator{
			Locale: uc.locale,
		},
	}
	uc.containerService, uc.apiVersion, err = apiloader.LoadContainerServiceFromFile(apiModelPath, true, true, nil)
	if err != nil {
		return fmt.Errorf("error parsing the api model: %s", err.Error())
	}

	if uc.containerService.Location == "" {
		uc.containerService.Location = uc.location
	} else if uc.containerService.Location != uc.location {
		return fmt.Errorf("--location does not match api model location")
	}

	// get available upgrades for container service
	orchestratorInfo, err := api.GetOrchestratorVersionProfile(uc.containerService.Properties.OrchestratorProfile)
	if err != nil {
		return fmt.Errorf("error getting list of available upgrades: %s", err.Error())
	}
	// add the current version if upgrade has failed
	orchestratorInfo.Upgrades = append(orchestratorInfo.Upgrades, &api.OrchestratorProfile{
		OrchestratorType:    uc.containerService.Properties.OrchestratorProfile.OrchestratorType,
		OrchestratorVersion: uc.containerService.Properties.OrchestratorProfile.OrchestratorVersion})

	// validate desired upgrade version and set goal state
	found := false
	for _, up := range orchestratorInfo.Upgrades {
		if up.OrchestratorVersion == uc.upgradeVersion {
			uc.containerService.Properties.OrchestratorProfile.OrchestratorVersion = uc.upgradeVersion
			found = true
			break
		}
	}
	if !found {
		return fmt.Errorf("version %s is not supported", uc.upgradeVersion)
	}

	// Read name suffix to identify nodes in the resource group that belong
	// to this cluster.
	// TODO: Also update to read  namesuffix from the parameters file as
	// user could have specified a name suffix instead of using the default
	// value generated by ACS Engine
	templatePath := path.Join(uc.deploymentDirectory, "azuredeploy.json")
	contents, _ := ioutil.ReadFile(templatePath)

	var template interface{}
	json.Unmarshal(contents, &template)

	templateMap := template.(map[string]interface{})
	templateParameters := templateMap["parameters"].(map[string]interface{})

	nameSuffixParam := templateParameters["nameSuffix"].(map[string]interface{})
	uc.nameSuffix = nameSuffixParam["defaultValue"].(string)
	log.Infoln(fmt.Sprintf("Name suffix: %s", uc.nameSuffix))

	uc.agentPoolsToUpgrade = []string{}
	log.Infoln(fmt.Sprintf("Gathering agent pool names..."))
	for _, agentPool := range uc.containerService.Properties.AgentPoolProfiles {
		uc.agentPoolsToUpgrade = append(uc.agentPoolsToUpgrade, agentPool.Name)
	}
	return nil
}

func (uc *upgradeCmd) run(cmd *cobra.Command, args []string) error {
	err := uc.validate(cmd)
	if err != nil {
		log.Fatalf("error validating upgrade command: %v", err)
	}

	err = uc.loadCluster(cmd)
	if err != nil {
		log.Fatalf("error loading existing cluster: %v", err)
	}

	upgradeCluster := kubernetesupgrade.UpgradeCluster{
		Translator: &i18n.Translator{
			Locale: uc.locale,
		},
		Logger:      log.NewEntry(log.New()),
		Client:      uc.client,
		StepTimeout: uc.timeout,
	}

	kubeConfig, err := acsengine.GenerateKubeConfig(uc.containerService.Properties, uc.location)
	if err != nil {
		log.Fatalf("failed to generate kube config: %v", err) // TODO: cleanup
	}

	if err = upgradeCluster.UpgradeCluster(uc.authArgs.SubscriptionID, kubeConfig, uc.resourceGroupName,
		uc.containerService, uc.nameSuffix, uc.agentPoolsToUpgrade, BuildTag); err != nil {
		log.Fatalf("Error upgrading cluster: %v\n", err)
	}

	apiloader := &api.Apiloader{
		Translator: &i18n.Translator{
			Locale: uc.locale,
		},
	}
	b, err := apiloader.SerializeContainerService(uc.containerService, uc.apiVersion)
	if err != nil {
		return err
	}

	f := acsengine.FileSaver{
		Translator: &i18n.Translator{
			Locale: uc.locale,
		},
	}

	return f.SaveFile(uc.deploymentDirectory, "apimodel.json", b)
}

{'fraction_non_alphanumeric': 0.12400898520084566, 'fraction_numerical': 0.0023784355179704017, 'mean_word_length': 4.300070028011205, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6222624', 'n_tokens_mistral': 5199, 'n_tokens_neox': 4895, 'n_words': 1186}