mirror of
https://github.com/rancher/cli.git
synced 2026-02-05 09:48:36 +01:00
Before this change, it was not possible to use the SSH command to connect to machines provisioned with v2 provisioning. After this change (and including the changes to Rancher), the CLI will use the new CAPI client to get the SSH key and config from Rancher for v2 provisioned machines. A side effect of this change is the addition of the new `rancher machines ls` command that lists all machines for the current cluster context.
723 lines
17 KiB
Go
723 lines
17 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"text/template"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/docker/docker/pkg/namesgenerator"
|
|
"github.com/ghodss/yaml"
|
|
"github.com/pkg/errors"
|
|
"github.com/rancher/cli/cliclient"
|
|
"github.com/rancher/cli/config"
|
|
"github.com/rancher/norman/clientbase"
|
|
ntypes "github.com/rancher/norman/types"
|
|
"github.com/rancher/norman/types/convert"
|
|
managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/urfave/cli"
|
|
"k8s.io/client-go/tools/clientcmd/api"
|
|
)
|
|
|
|
const (
|
|
letters = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
cfgFile = "cli2.json"
|
|
kubeConfigKeyFormat = "%s-%s"
|
|
)
|
|
|
|
var (
|
|
errNoURL = errors.New("RANCHER_URL environment or --Url is not set, run `login`")
|
|
// ManagementResourceTypes lists the types we use the management client for
|
|
ManagementResourceTypes = []string{"cluster", "node", "project"}
|
|
// ProjectResourceTypes lists the types we use the cluster client for
|
|
ProjectResourceTypes = []string{"secret", "namespacedSecret", "workload"}
|
|
// ClusterResourceTypes lists the types we use the project client for
|
|
ClusterResourceTypes = []string{"persistentVolume", "storageClass", "namespace"}
|
|
|
|
formatFlag = cli.StringFlag{
|
|
Name: "format,o",
|
|
Usage: "'json', 'yaml' or custom format",
|
|
}
|
|
|
|
quietFlag = cli.BoolFlag{
|
|
Name: "quiet,q",
|
|
Usage: "Only display IDs or suppress help text",
|
|
}
|
|
)
|
|
|
|
type MemberData struct {
|
|
Name string
|
|
MemberType string
|
|
AccessType string
|
|
}
|
|
|
|
type RoleTemplate struct {
|
|
ID string
|
|
Name string
|
|
Description string
|
|
}
|
|
|
|
type RoleTemplateBinding struct {
|
|
ID string
|
|
User string
|
|
Role string
|
|
Created string
|
|
}
|
|
|
|
func listAllRoles() []string {
|
|
roles := []string{}
|
|
roles = append(roles, ManagementResourceTypes...)
|
|
roles = append(roles, ProjectResourceTypes...)
|
|
roles = append(roles, ClusterResourceTypes...)
|
|
return roles
|
|
}
|
|
|
|
func listRoles(ctx *cli.Context, context string) error {
|
|
c, err := GetClient(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
filter := defaultListOpts(ctx)
|
|
filter.Filters["hidden"] = false
|
|
filter.Filters["context"] = context
|
|
|
|
templates, err := c.ManagementClient.RoleTemplate.List(filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
writer := NewTableWriter([][]string{
|
|
{"ID", "ID"},
|
|
{"NAME", "Name"},
|
|
{"DESCRIPTION", "Description"},
|
|
}, ctx)
|
|
|
|
defer writer.Close()
|
|
|
|
for _, item := range templates.Data {
|
|
writer.Write(&RoleTemplate{
|
|
ID: item.ID,
|
|
Name: item.Name,
|
|
Description: item.Description,
|
|
})
|
|
}
|
|
|
|
return writer.Err()
|
|
}
|
|
|
|
func listRoleTemplateBindings(ctx *cli.Context, b []RoleTemplateBinding) error {
|
|
writer := NewTableWriter([][]string{
|
|
{"BINDING-ID", "ID"},
|
|
{"USER", "User"},
|
|
{"ROLE", "Role"},
|
|
{"CREATED", "Created"},
|
|
}, ctx)
|
|
|
|
defer writer.Close()
|
|
|
|
for _, item := range b {
|
|
writer.Write(&RoleTemplateBinding{
|
|
ID: item.ID,
|
|
User: item.User,
|
|
Role: item.Role,
|
|
Created: item.Created,
|
|
})
|
|
}
|
|
return writer.Err()
|
|
}
|
|
|
|
func getKubeConfigForUser(ctx *cli.Context, user string) (*api.Config, error) {
|
|
cf, err := loadConfig(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
focusedServer := cf.FocusedServer()
|
|
kubeConfig, _ := focusedServer.KubeConfigs[fmt.Sprintf(kubeConfigKeyFormat, user, focusedServer.FocusedCluster())]
|
|
return kubeConfig, nil
|
|
}
|
|
|
|
func setKubeConfigForUser(ctx *cli.Context, user string, kubeConfig *api.Config) error {
|
|
cf, err := loadConfig(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cf.FocusedServer().KubeConfigs == nil {
|
|
cf.FocusedServer().KubeConfigs = make(map[string]*api.Config)
|
|
}
|
|
focusedServer := cf.FocusedServer()
|
|
focusedServer.KubeConfigs[fmt.Sprintf(kubeConfigKeyFormat, user, focusedServer.FocusedCluster())] = kubeConfig
|
|
return cf.Write()
|
|
}
|
|
|
|
func usersToNameMapping(u []managementClient.User) map[string]string {
|
|
userMapping := make(map[string]string)
|
|
for _, user := range u {
|
|
if user.Name != "" {
|
|
userMapping[user.ID] = user.Name
|
|
|
|
} else {
|
|
userMapping[user.ID] = user.Username
|
|
}
|
|
}
|
|
return userMapping
|
|
}
|
|
|
|
func searchForMember(ctx *cli.Context, c *cliclient.MasterClient, name string) (*managementClient.Principal, error) {
|
|
filter := defaultListOpts(ctx)
|
|
filter.Filters["ID"] = "thisisnotathingIhope"
|
|
|
|
// A collection is needed to get the action link
|
|
pCollection, err := c.ManagementClient.Principal.List(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := managementClient.SearchPrincipalsInput{
|
|
Name: name,
|
|
}
|
|
|
|
results, err := c.ManagementClient.Principal.CollectionActionSearch(pCollection, &p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dataLength := len(results.Data)
|
|
switch {
|
|
case dataLength == 0:
|
|
return nil, fmt.Errorf("no results found for %q", name)
|
|
case dataLength == 1:
|
|
return &results.Data[0], nil
|
|
case dataLength >= 10:
|
|
results.Data = results.Data[:10]
|
|
}
|
|
|
|
var names []string
|
|
|
|
for _, person := range results.Data {
|
|
names = append(names, person.Name+fmt.Sprintf(" (%s)", person.PrincipalType))
|
|
}
|
|
selection := selectFromList("Multiple results found:", names)
|
|
|
|
return &results.Data[selection], nil
|
|
}
|
|
|
|
func getRancherServerVersion(c *cliclient.MasterClient) (string, error) {
|
|
setting, err := c.ManagementClient.Setting.ByID("server-version")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return setting.Value, err
|
|
}
|
|
|
|
func loadAndVerifyCert(path string) (string, error) {
|
|
caCert, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return verifyCert(caCert)
|
|
}
|
|
|
|
func verifyCert(caCert []byte) (string, error) {
|
|
// replace the escaped version of the line break
|
|
caCert = bytes.Replace(caCert, []byte(`\n`), []byte("\n"), -1)
|
|
|
|
block, _ := pem.Decode(caCert)
|
|
|
|
if nil == block {
|
|
return "", errors.New("No cert was found")
|
|
}
|
|
|
|
parsedCert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if !parsedCert.IsCA {
|
|
return "", errors.New("CACerts is not valid")
|
|
}
|
|
return string(caCert), nil
|
|
}
|
|
|
|
func loadConfig(ctx *cli.Context) (config.Config, error) {
|
|
// path will always be set by the global flag default
|
|
path := ctx.GlobalString("config")
|
|
path = filepath.Join(path, cfgFile)
|
|
|
|
cf := config.Config{
|
|
Path: path,
|
|
Servers: make(map[string]*config.ServerConfig),
|
|
}
|
|
|
|
content, err := ioutil.ReadFile(path)
|
|
if os.IsNotExist(err) {
|
|
return cf, nil
|
|
} else if err != nil {
|
|
return cf, err
|
|
}
|
|
|
|
err = json.Unmarshal(content, &cf)
|
|
cf.Path = path
|
|
|
|
return cf, err
|
|
}
|
|
|
|
func lookupConfig(ctx *cli.Context) (*config.ServerConfig, error) {
|
|
cf, err := loadConfig(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cs := cf.FocusedServer()
|
|
if cs == nil {
|
|
return nil, errors.New("no configuration found, run `login`")
|
|
}
|
|
|
|
return cs, nil
|
|
}
|
|
|
|
func GetClient(ctx *cli.Context) (*cliclient.MasterClient, error) {
|
|
cf, err := lookupConfig(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mc, err := cliclient.NewMasterClient(cf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mc, nil
|
|
}
|
|
|
|
// GetResourceType maps an incoming resource type to a valid one from the schema
|
|
func GetResourceType(c *cliclient.MasterClient, resource string) (string, error) {
|
|
if c.ManagementClient != nil {
|
|
for key := range c.ManagementClient.APIBaseClient.Types {
|
|
if strings.ToLower(key) == strings.ToLower(resource) {
|
|
return key, nil
|
|
}
|
|
}
|
|
}
|
|
if c.ProjectClient != nil {
|
|
for key := range c.ProjectClient.APIBaseClient.Types {
|
|
if strings.ToLower(key) == strings.ToLower(resource) {
|
|
return key, nil
|
|
}
|
|
}
|
|
}
|
|
if c.ClusterClient != nil {
|
|
for key := range c.ClusterClient.APIBaseClient.Types {
|
|
if strings.ToLower(key) == strings.ToLower(resource) {
|
|
return key, nil
|
|
}
|
|
}
|
|
}
|
|
if c.CAPIClient != nil {
|
|
for key := range c.CAPIClient.APIBaseClient.Types {
|
|
lowerKey := strings.ToLower(key)
|
|
if strings.HasPrefix(lowerKey, "cluster.x-k8s.io") && lowerKey == strings.ToLower(resource) {
|
|
return key, nil
|
|
}
|
|
}
|
|
}
|
|
return "", fmt.Errorf("unknown resource type: %s", resource)
|
|
}
|
|
|
|
func Lookup(c *cliclient.MasterClient, name string, types ...string) (*ntypes.Resource, error) {
|
|
var byName *ntypes.Resource
|
|
|
|
for _, schemaType := range types {
|
|
rt, err := GetResourceType(c, schemaType)
|
|
if err != nil {
|
|
logrus.Debugf("Error GetResourceType: %v", err)
|
|
return nil, err
|
|
}
|
|
var schemaClient clientbase.APIBaseClientInterface
|
|
// the schemaType dictates which client we need to use
|
|
if c.CAPIClient != nil {
|
|
if strings.HasPrefix(rt, "cluster.x-k8s.io") {
|
|
schemaClient = c.CAPIClient
|
|
}
|
|
}
|
|
if c.ManagementClient != nil {
|
|
if _, ok := c.ManagementClient.APIBaseClient.Types[rt]; ok {
|
|
schemaClient = c.ManagementClient
|
|
}
|
|
}
|
|
if c.ProjectClient != nil {
|
|
if _, ok := c.ProjectClient.APIBaseClient.Types[rt]; ok {
|
|
schemaClient = c.ProjectClient
|
|
}
|
|
}
|
|
if c.ClusterClient != nil {
|
|
if _, ok := c.ClusterClient.APIBaseClient.Types[rt]; ok {
|
|
schemaClient = c.ClusterClient
|
|
}
|
|
}
|
|
|
|
// Attempt to get the resource by ID
|
|
var resource ntypes.Resource
|
|
|
|
if err := schemaClient.ByID(schemaType, name, &resource); !clientbase.IsNotFound(err) && err != nil {
|
|
logrus.Debugf("Error schemaClient.ByID: %v", err)
|
|
return nil, err
|
|
} else if err == nil && resource.ID == name {
|
|
return &resource, nil
|
|
}
|
|
|
|
// Resource was not found assuming the ID, check if it's the name of a resource
|
|
var collection ntypes.ResourceCollection
|
|
|
|
listOpts := &ntypes.ListOpts{
|
|
Filters: map[string]interface{}{
|
|
"name": name,
|
|
"removed_null": 1,
|
|
},
|
|
}
|
|
|
|
if err := schemaClient.List(schemaType, listOpts, &collection); !clientbase.IsNotFound(err) && err != nil {
|
|
logrus.Debugf("Error schemaClient.List: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
if len(collection.Data) > 1 {
|
|
ids := []string{}
|
|
for _, data := range collection.Data {
|
|
ids = append(ids, data.ID)
|
|
}
|
|
return nil, fmt.Errorf("Multiple resources of type %s found for name %s: %v", schemaType, name, ids)
|
|
}
|
|
|
|
// No matches for this schemaType, try the next one
|
|
if len(collection.Data) == 0 {
|
|
continue
|
|
}
|
|
|
|
if byName != nil {
|
|
return nil, fmt.Errorf("Multiple resources named %s: %s:%s, %s:%s", name, collection.Data[0].Type,
|
|
collection.Data[0].ID, byName.Type, byName.ID)
|
|
}
|
|
|
|
byName = &collection.Data[0]
|
|
|
|
}
|
|
|
|
if byName == nil {
|
|
return nil, fmt.Errorf("Not found: %s", name)
|
|
}
|
|
|
|
return byName, nil
|
|
}
|
|
|
|
func RandomName() string {
|
|
return strings.Replace(namesgenerator.GetRandomName(0), "_", "-", -1)
|
|
}
|
|
|
|
// RandomLetters returns a string with random letters of length n
|
|
func RandomLetters(n int) string {
|
|
rand.Seed(time.Now().UnixNano())
|
|
b := make([]byte, n)
|
|
for i := range b {
|
|
b[i] = letters[rand.Intn(len(letters))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func appendTabDelim(buf *bytes.Buffer, value string) {
|
|
if buf.Len() == 0 {
|
|
buf.WriteString(value)
|
|
} else {
|
|
buf.WriteString("\t")
|
|
buf.WriteString(value)
|
|
}
|
|
}
|
|
|
|
func SimpleFormat(values [][]string) (string, string) {
|
|
headerBuffer := bytes.Buffer{}
|
|
valueBuffer := bytes.Buffer{}
|
|
for _, v := range values {
|
|
appendTabDelim(&headerBuffer, v[0])
|
|
if strings.Contains(v[1], "{{") {
|
|
appendTabDelim(&valueBuffer, v[1])
|
|
} else {
|
|
appendTabDelim(&valueBuffer, "{{."+v[1]+"}}")
|
|
}
|
|
}
|
|
|
|
headerBuffer.WriteString("\n")
|
|
valueBuffer.WriteString("\n")
|
|
|
|
return headerBuffer.String(), valueBuffer.String()
|
|
}
|
|
|
|
func defaultAction(fn func(ctx *cli.Context) error) func(ctx *cli.Context) error {
|
|
return func(ctx *cli.Context) error {
|
|
if ctx.Bool("help") {
|
|
cli.ShowAppHelp(ctx)
|
|
return nil
|
|
}
|
|
return fn(ctx)
|
|
}
|
|
}
|
|
|
|
func printTemplate(out io.Writer, templateContent string, obj interface{}) error {
|
|
funcMap := map[string]interface{}{
|
|
"endpoint": FormatEndpoint,
|
|
"ips": FormatIPAddresses,
|
|
"json": FormatJSON,
|
|
}
|
|
tmpl, err := template.New("").Funcs(funcMap).Parse(templateContent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tmpl.Execute(out, obj)
|
|
}
|
|
|
|
func selectFromList(header string, choices []string) int {
|
|
if header != "" {
|
|
fmt.Println(header)
|
|
}
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
selected := -1
|
|
for selected <= 0 || selected > len(choices) {
|
|
for i, choice := range choices {
|
|
fmt.Printf("[%d] %s\n", i+1, choice)
|
|
}
|
|
fmt.Print("Select: ")
|
|
|
|
text, _ := reader.ReadString('\n')
|
|
text = strings.TrimSpace(text)
|
|
num, err := strconv.Atoi(text)
|
|
if err == nil {
|
|
selected = num
|
|
}
|
|
}
|
|
return selected - 1
|
|
}
|
|
|
|
func processExitCode(err error) error {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
os.Exit(status.ExitStatus())
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func SplitOnColon(s string) []string {
|
|
return strings.Split(s, ":")
|
|
}
|
|
|
|
func parseClusterAndProjectName(name string) (string, string, error) {
|
|
parts := strings.SplitN(name, "/", 2)
|
|
if len(parts) == 2 {
|
|
return parts[0], parts[1], nil
|
|
}
|
|
return "", "", fmt.Errorf("Unable to extract clustername and projectname from [%s]", name)
|
|
}
|
|
|
|
func parseClusterAndProjectID(id string) (string, string, error) {
|
|
// Validate id
|
|
// Examples:
|
|
// c-qmpbm:p-mm62v
|
|
// c-qmpbm:project-mm62v
|
|
// c-m-j2s7m6lq:p-mm62v
|
|
// See https://github.com/rancher/rancher/issues/14400
|
|
if match, _ := regexp.MatchString("((local)|(c-[[:alnum:]]{5})|(c-m-[[:alnum:]]{8})):(p|project)-[[:alnum:]]{5}", id); match {
|
|
parts := SplitOnColon(id)
|
|
return parts[0], parts[1], nil
|
|
}
|
|
return "", "", fmt.Errorf("Unable to extract clusterid and projectid from [%s]", id)
|
|
}
|
|
|
|
func fixTopLevelKeys(bytes []byte) ([]byte, error) {
|
|
old := map[string]interface{}{}
|
|
new := map[string]interface{}{}
|
|
|
|
err := json.Unmarshal(bytes, &old)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error unmarshalling: %v", err)
|
|
}
|
|
|
|
for key, val := range old {
|
|
newKey := convert.ToJSONKey(key)
|
|
new[newKey] = val
|
|
}
|
|
|
|
return json.Marshal(new)
|
|
}
|
|
|
|
// Return a JSON blob of the file at path
|
|
func readFileReturnJSON(path string) ([]byte, error) {
|
|
file, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
// This is probably already JSON if true
|
|
if hasPrefix(file, []byte("{")) {
|
|
return file, nil
|
|
}
|
|
return yaml.YAMLToJSON(file)
|
|
}
|
|
|
|
// Return true if the first non-whitespace bytes in buf is prefix.
|
|
func hasPrefix(buf []byte, prefix []byte) bool {
|
|
trim := bytes.TrimLeftFunc(buf, unicode.IsSpace)
|
|
return bytes.HasPrefix(trim, prefix)
|
|
}
|
|
|
|
func settingsToMap(client *cliclient.MasterClient) (map[string]string, error) {
|
|
configMap := make(map[string]string)
|
|
|
|
settings, err := client.ManagementClient.Setting.List(baseListOpts())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, setting := range settings.Data {
|
|
configMap[setting.Name] = setting.Value
|
|
}
|
|
|
|
return configMap, nil
|
|
}
|
|
|
|
// getClusterNames maps cluster ID to name and defaults to ID if name is blank
|
|
func getClusterNames(ctx *cli.Context, c *cliclient.MasterClient) (map[string]string, error) {
|
|
clusterNames := make(map[string]string)
|
|
clusterCollection, err := c.ManagementClient.Cluster.List(defaultListOpts(ctx))
|
|
if err != nil {
|
|
return clusterNames, err
|
|
}
|
|
|
|
for _, cluster := range clusterCollection.Data {
|
|
if cluster.Name == "" {
|
|
clusterNames[cluster.ID] = cluster.ID
|
|
} else {
|
|
clusterNames[cluster.ID] = cluster.Name
|
|
}
|
|
}
|
|
return clusterNames, nil
|
|
}
|
|
|
|
func getClusterName(cluster *managementClient.Cluster) string {
|
|
if cluster.Name != "" {
|
|
return cluster.Name
|
|
}
|
|
return cluster.ID
|
|
}
|
|
|
|
func findStringInArray(s string, a []string) bool {
|
|
for _, val := range a {
|
|
if s == val {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func createdTimetoHuman(t string) (string, error) {
|
|
parsedTime, err := time.Parse(time.RFC3339, t)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return parsedTime.Format("02 Jan 2006 15:04:05 MST"), nil
|
|
}
|
|
|
|
func outputMembers(ctx *cli.Context, c *cliclient.MasterClient, members []managementClient.Member) error {
|
|
writer := NewTableWriter([][]string{
|
|
{"NAME", "Name"},
|
|
{"MEMBER_TYPE", "MemberType"},
|
|
{"ACCESS_TYPE", "AccessType"},
|
|
}, ctx)
|
|
|
|
defer writer.Close()
|
|
|
|
for _, m := range members {
|
|
principalID := m.UserPrincipalID
|
|
if m.UserPrincipalID == "" {
|
|
principalID = m.GroupPrincipalID
|
|
}
|
|
principal, err := c.ManagementClient.Principal.ByID(url.PathEscape(principalID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer.Write(&MemberData{
|
|
Name: principal.Name,
|
|
MemberType: strings.Title(fmt.Sprintf("%s %s", principal.Provider, principal.PrincipalType)),
|
|
AccessType: m.AccessType,
|
|
})
|
|
}
|
|
return writer.Err()
|
|
}
|
|
|
|
func addMembersByNames(ctx *cli.Context, c *cliclient.MasterClient, members []managementClient.Member, toAddMembers []string, accessType string) ([]managementClient.Member, error) {
|
|
for _, name := range toAddMembers {
|
|
member, err := searchForMember(ctx, c, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
toAddMember := managementClient.Member{
|
|
AccessType: accessType,
|
|
}
|
|
if member.PrincipalType == "user" {
|
|
toAddMember.UserPrincipalID = member.ID
|
|
} else {
|
|
toAddMember.GroupPrincipalID = member.ID
|
|
}
|
|
members = append(members, toAddMember)
|
|
}
|
|
return members, nil
|
|
}
|
|
|
|
func deleteMembersByNames(ctx *cli.Context, c *cliclient.MasterClient, members []managementClient.Member, todeleteMembers []string) ([]managementClient.Member, error) {
|
|
for _, name := range todeleteMembers {
|
|
member, err := searchForMember(ctx, c, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var toKeepMembers []managementClient.Member
|
|
for _, m := range members {
|
|
if m.GroupPrincipalID != member.ID && m.UserPrincipalID != member.ID {
|
|
toKeepMembers = append(toKeepMembers, m)
|
|
}
|
|
}
|
|
members = toKeepMembers
|
|
}
|
|
return members, nil
|
|
}
|
|
|
|
func tickerContext(ctx context.Context, duration time.Duration) <-chan time.Time {
|
|
ticker := time.NewTicker(duration)
|
|
go func() {
|
|
<-ctx.Done()
|
|
ticker.Stop()
|
|
}()
|
|
return ticker.C
|
|
}
|