<commit_before>/*
   Copyright 2014 Outbrain Inc.

   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.
*/

package app

import (
	"fmt"
	"net"
	"os"
	"os/user"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/github/orchestrator/go/agent"
	"github.com/github/orchestrator/go/config"
	"github.com/github/orchestrator/go/inst"
	"github.com/github/orchestrator/go/logic"
	"github.com/github/orchestrator/go/process"
	"github.com/github/orchestrator/go/remote"
	"github.com/openark/golib/log"
	"github.com/openark/golib/util"
)

var thisInstanceKey *inst.InstanceKey
var knownCommands []CliCommand

type CliCommand struct {
	Command     string
	Section     string
	Description string
}

type stringSlice []string

func (a stringSlice) Len() int           { return len(a) }
func (a stringSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a stringSlice) Less(i, j int) bool { return a[i] < a[j] }

var commandSynonyms = map[string]string{
	"relocate-slaves":             "relocate-replicas",
	"regroup-slaves":              "regroup-replicas",
	"move-up-slaves":              "move-up-replicas",
	"repoint-slaves":              "repoint-replicas",
	"enslave-siblings":            "take-siblings",
	"enslave-master":              "take-master",
	"get-candidate-slave":         "get-candidate-replica",
	"move-slaves-gtid":            "move-replicas-gtid",
	"regroup-slaves-gtid":         "regroup-replicas-gtid",
	"match-slaves":                "match-replicas",
	"match-up-slaves":             "match-up-replicas",
	"regroup-slaves-pgtid":        "regroup-replicas-pgtid",
	"which-cluster-osc-slaves":    "which-cluster-osc-replicas",
	"which-cluster-gh-ost-slaves": "which-cluster-gh-ost-replicas",
	"which-slaves":                "which-replicas",
	"detach-slave":                "detach-replica",
	"reattach-slave":              "reattach-replica",
	"detach-slave-master-host":    "detach-replica-master-host",
	"reattach-slave-master-host":  "reattach-replica-master-host",
}

func registerCliCommand(command string, section string, description string) string {
	if synonym, ok := commandSynonyms[command]; ok {
		command = synonym
	}
	knownCommands = append(knownCommands, CliCommand{Command: command, Section: section, Description: description})

	return command
}

func commandsListing() string {
	listing := []string{}
	lastSection := ""
	for _, cliCommand := range knownCommands {
		if lastSection != cliCommand.Section {
			lastSection = cliCommand.Section
			listing = append(listing, fmt.Sprintf("%s:", cliCommand.Section))
		}
		commandListing := fmt.Sprintf("\t%-40s%s", cliCommand.Command, cliCommand.Description)
		listing = append(listing, commandListing)
	}
	return strings.Join(listing, "\n")
}

func availableCommandsUsage() string {
	return fmt.Sprintf(`Available commands (-c):
%+v
Run 'orchestrator help <command>' for detailed help on given command, e.g. 'orchestrator help relocate'

Usage for most commands:
	orchestrator -c <command> [-i <instance.fqdn>[,<instance.fqdn>]* ] [-d <destination.fqdn>] [--verbose|--debug]
`, commandsListing())
}

// getClusterName will make a best effort to deduce a cluster name using either a given alias
// or an instanceKey. First attempt is at alias, and if that doesn't work, we try instanceKey.
func getClusterName(clusterAlias string, instanceKey *inst.InstanceKey) (clusterName string) {
	clusterName, _ = inst.FigureClusterName(clusterAlias, instanceKey, thisInstanceKey)
	return clusterName
}

func assignThisInstanceKey() *inst.InstanceKey {
	log.Debugf("Assuming instance is this machine, %+v", thisInstanceKey)
	return thisInstanceKey
}

func validateInstanceIsFound(instanceKey *inst.InstanceKey) (instance *inst.Instance) {
	instance, _, err := inst.ReadInstance(instanceKey)
	if err != nil {
		log.Fatale(err)
	}
	if instance == nil {
		log.Fatalf("Instance not found: %+v", *instanceKey)
	}
	return instance
}

// CliWrapper is called from main and allows for the instance parameter
// to take multiple instance names separated by a comma or whitespace.
func CliWrapper(command string, strict bool, instances string, destination string, owner string, reason string, duration string, pattern string, clusterAlias string, pool string, hostnameFlag string) {
	r := regexp.MustCompile(`[ ,\r\n\t]+`)<commit_msg>CLI forbidden when RaftEnabled is true<commit_after>/*
   Copyright 2014 Outbrain Inc.

   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.
*/

package app

import (
	"fmt"
	"net"
	"os"
	"os/user"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/github/orchestrator/go/agent"
	"github.com/github/orchestrator/go/config"
	"github.com/github/orchestrator/go/inst"
	"github.com/github/orchestrator/go/logic"
	"github.com/github/orchestrator/go/process"
	"github.com/github/orchestrator/go/remote"
	"github.com/openark/golib/log"
	"github.com/openark/golib/util"
)

var thisInstanceKey *inst.InstanceKey
var knownCommands []CliCommand

type CliCommand struct {
	Command     string
	Section     string
	Description string
}

type stringSlice []string

func (a stringSlice) Len() int           { return len(a) }
func (a stringSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a stringSlice) Less(i, j int) bool { return a[i] < a[j] }

var commandSynonyms = map[string]string{
	"relocate-slaves":             "relocate-replicas",
	"regroup-slaves":              "regroup-replicas",
	"move-up-slaves":              "move-up-replicas",
	"repoint-slaves":              "repoint-replicas",
	"enslave-siblings":            "take-siblings",
	"enslave-master":              "take-master",
	"get-candidate-slave":         "get-candidate-replica",
	"move-slaves-gtid":            "move-replicas-gtid",
	"regroup-slaves-gtid":         "regroup-replicas-gtid",
	"match-slaves":                "match-replicas",
	"match-up-slaves":             "match-up-replicas",
	"regroup-slaves-pgtid":        "regroup-replicas-pgtid",
	"which-cluster-osc-slaves":    "which-cluster-osc-replicas",
	"which-cluster-gh-ost-slaves": "which-cluster-gh-ost-replicas",
	"which-slaves":                "which-replicas",
	"detach-slave":                "detach-replica",
	"reattach-slave":              "reattach-replica",
	"detach-slave-master-host":    "detach-replica-master-host",
	"reattach-slave-master-host":  "reattach-replica-master-host",
}

func registerCliCommand(command string, section string, description string) string {
	if synonym, ok := commandSynonyms[command]; ok {
		command = synonym
	}
	knownCommands = append(knownCommands, CliCommand{Command: command, Section: section, Description: description})

	return command
}

func commandsListing() string {
	listing := []string{}
	lastSection := ""
	for _, cliCommand := range knownCommands {
		if lastSection != cliCommand.Section {
			lastSection = cliCommand.Section
			listing = append(listing, fmt.Sprintf("%s:", cliCommand.Section))
		}
		commandListing := fmt.Sprintf("\t%-40s%s", cliCommand.Command, cliCommand.Description)
		listing = append(listing, commandListing)
	}
	return strings.Join(listing, "\n")
}

func availableCommandsUsage() string {
	return fmt.Sprintf(`Available commands (-c):
%+v
Run 'orchestrator help <command>' for detailed help on given command, e.g. 'orchestrator help relocate'

Usage for most commands:
	orchestrator -c <command> [-i <instance.fqdn>[,<instance.fqdn>]* ] [-d <destination.fqdn>] [--verbose|--debug]
`, commandsListing())
}

// getClusterName will make a best effort to deduce a cluster name using either a given alias
// or an instanceKey. First attempt is at alias, and if that doesn't work, we try instanceKey.
func getClusterName(clusterAlias string, instanceKey *inst.InstanceKey) (clusterName string) {
	clusterName, _ = inst.FigureClusterName(clusterAlias, instanceKey, thisInstanceKey)
	return clusterName
}

func assignThisInstanceKey() *inst.InstanceKey {
	log.Debugf("Assuming instance is this machine, %+v", thisInstanceKey)
	return thisInstanceKey
}

func validateInstanceIsFound(instanceKey *inst.InstanceKey) (instance *inst.Instance) {
	instance, _, err := inst.ReadInstance(instanceKey)
	if err != nil {
		log.Fatale(err)
	}
	if instance == nil {
		log.Fatalf("Instance not found: %+v", *instanceKey)
	}
	return instance
}

// CliWrapper is called from main and allows for the instance parameter
// to take multiple instance names separated by a comma or whitespace.
func CliWrapper(command string, strict bool, instances string, destination string, owner string, reason string, duration string, pattern string, clusterAlias string, pool string, hostnameFlag string) {
	if config.Config.RaftEnabled {
		log.Fatalf(`Orchestrator configured to run raft ("RaftEnabled": true). All access must go through the web API of the active raft node. You may use the orchestrator-client script which has a similar interface to the command line invocation.`)
	}
	r := regexp.MustCompile(`[ ,\r\n\t]+`)
{'fraction_non_alphanumeric': 0.12244484922080551, 'fraction_numerical': 0.0020238818053025702, 'mean_word_length': 3.8901533894111826, 'pattern_counts': {'":': 39, '<': 15, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '1400629', 'n_tokens_mistral': 3212, 'n_tokens_neox': 3149, 'n_words': 850}