github.com/fnproject/cli@v0.0.0-20240508150455-e5d88bd86117/objects/context/context.go (about)

     1  /*
     2   * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package context
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net/url"
    25  	"os"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strings"
    29  	"text/tabwriter"
    30  
    31  	"github.com/fnproject/cli/config"
    32  	"github.com/fnproject/fn_go/provider"
    33  	"github.com/spf13/viper"
    34  	"github.com/urfave/cli"
    35  )
    36  
    37  var contextsPath = config.GetContextsPath()
    38  var fileExtension = ".yaml"
    39  
    40  type ContextMap config.ContextMap
    41  
    42  func createCtx(c *cli.Context) error {
    43  	context := c.Args().Get(0)
    44  
    45  	err := ValidateContextName(context)
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	providerId := config.DefaultProvider
    51  	if cProvider := c.String("provider"); cProvider != "" {
    52  		providerId = cProvider
    53  	}
    54  
    55  	apiURL := ""
    56  	if cApiURL := c.String("api-url"); cApiURL != "" {
    57  		err = ValidateAPIURL(cApiURL)
    58  		if err != nil {
    59  			return err
    60  		}
    61  		apiURL = cApiURL
    62  	}
    63  
    64  	registry := ""
    65  	if cRegistry := c.String("registry"); cRegistry != "" {
    66  		registry = cRegistry
    67  	}
    68  
    69  	if check, err := checkContextFileExists(context); check {
    70  		if err != nil {
    71  			return err
    72  		}
    73  		return errors.New("Context already exists")
    74  	}
    75  	path := createFilePath(context + fileExtension)
    76  	file, err := os.Create(path)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	defer file.Close()
    81  
    82  	contextValues := &config.ContextMap{
    83  		config.ContextProvider: providerId,
    84  		provider.CfgFnAPIURL:   apiURL,
    85  		config.EnvFnRegistry:   registry,
    86  	}
    87  
    88  	err = config.WriteYamlFile(file.Name(), contextValues)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	fmt.Printf("Successfully created context: %v \n", context)
    94  	return nil
    95  }
    96  
    97  func deleteCtx(c *cli.Context) error {
    98  	context := c.Args().Get(0)
    99  
   100  	if check, err := checkContextFileExists(context); !check {
   101  		if err != nil {
   102  			return err
   103  		}
   104  		return errors.New("Context file not found")
   105  	}
   106  
   107  	if context == viper.GetString(config.CurrentContext) {
   108  		return fmt.Errorf("Cannot delete the current context: %v", context)
   109  	}
   110  
   111  	if context == "default" {
   112  		return errors.New("Cannot delete default context")
   113  	}
   114  
   115  	path := createFilePath(context + fileExtension)
   116  	err := os.Remove(path)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	fmt.Printf("Successfully deleted context %v \n", context)
   122  	return nil
   123  }
   124  
   125  func inspectCtx(c *cli.Context) error {
   126  	context := c.Args().Get(0)
   127  	if context == "" {
   128  		if currentContext := viper.GetString(config.CurrentContext); currentContext != "" {
   129  			context = currentContext
   130  		} else {
   131  			return errors.New("no context is set, please provider a context to inspect.")
   132  		}
   133  	}
   134  	return printContext(context)
   135  }
   136  
   137  func printContext(context string) error {
   138  	if check, err := checkContextFileExists(context); !check {
   139  		if err != nil {
   140  			return err
   141  		}
   142  		return errors.New("Context file not found")
   143  	}
   144  
   145  	contextPath := filepath.Join(config.GetHomeDir(), ".fn", "contexts", (context + fileExtension))
   146  	b, err := ioutil.ReadFile(contextPath)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	fmt.Printf("Current context: %s\n\n", context)
   152  	fmt.Println(string(b))
   153  	return nil
   154  }
   155  
   156  func useCtx(c *cli.Context) error {
   157  	context := c.Args().Get(0)
   158  
   159  	if check, err := checkContextFileExists(context); !check {
   160  		if err != nil {
   161  			return err
   162  		}
   163  		return errors.New("Context file not found")
   164  	}
   165  
   166  	if context == viper.GetString(config.CurrentContext) {
   167  		return fmt.Errorf("Context %v currently in use", context)
   168  	}
   169  
   170  	err := config.WriteConfigValueToConfigFile(config.CurrentContext, context)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	viper.Set(config.CurrentContext, context)
   175  
   176  	fmt.Printf("Now using context: %v \n", context)
   177  	return nil
   178  }
   179  
   180  func unsetCtx(c *cli.Context) error {
   181  	if currentContext := viper.GetString(config.CurrentContext); currentContext == "" {
   182  		return errors.New("No context currently in use")
   183  	}
   184  
   185  	err := config.WriteConfigValueToConfigFile(config.CurrentContext, "")
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	fmt.Printf("Successfully unset current context \n")
   191  	return nil
   192  }
   193  
   194  func printContexts(c *cli.Context, contexts []*Info) error {
   195  	outputFormat := strings.ToLower(c.String("output"))
   196  	if outputFormat == "json" {
   197  		b, err := json.MarshalIndent(contexts, "", "    ")
   198  		if err != nil {
   199  			return err
   200  		}
   201  		fmt.Fprint(os.Stdout, string(b))
   202  	} else {
   203  		w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
   204  		fmt.Fprint(w, "CURRENT", "\t", "NAME", "\t", "PROVIDER", "\t", "API URL", "\t", "REGISTRY", "\n")
   205  
   206  		for _, ctx := range contexts {
   207  			current := ""
   208  			if ctx.Current {
   209  				current = "*"
   210  			}
   211  			fmt.Fprint(w, current, "\t", ctx.Name, "\t", ctx.ContextProvider, "\t", ctx.EnvFnAPIURL, "\t", ctx.EnvFnRegistry, "\n")
   212  		}
   213  		if err := w.Flush(); err != nil {
   214  			return err
   215  		}
   216  	}
   217  	return nil
   218  }
   219  
   220  func listCtx(c *cli.Context) error {
   221  	contexts, err := getAvailableContexts()
   222  	if err != nil {
   223  		return err
   224  	}
   225  	return printContexts(c, contexts)
   226  }
   227  
   228  func (ctxMap *ContextMap) updateCtx(c *cli.Context) error {
   229  	key := c.Args().Get(0)
   230  
   231  	delete := c.Bool("delete")
   232  	if delete {
   233  		err := ctxMap.UnSet(key)
   234  		if err != nil {
   235  			return err
   236  		}
   237  		fmt.Printf("Current context deleted %v \n", key)
   238  		return nil
   239  	}
   240  
   241  	value := c.Args().Get(1)
   242  	if value == "" {
   243  		return errors.New("Please specify a value")
   244  	}
   245  
   246  	err := ctxMap.Set(key, value)
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	fmt.Printf("Current context updated %v with %v\n", key, value)
   252  	return err
   253  }
   254  
   255  func createFilePath(filename string) string {
   256  	home := config.GetHomeDir()
   257  	return filepath.Join(home, contextsPath, filename)
   258  }
   259  
   260  func checkContextFileExists(filename string) (bool, error) {
   261  	path := createFilePath(filename + fileExtension)
   262  
   263  	if _, err := os.Stat(path); os.IsNotExist(err) {
   264  		return false, err
   265  	}
   266  	return true, nil
   267  }
   268  
   269  func getAvailableContexts() ([]*Info, error) {
   270  	home := config.GetHomeDir()
   271  	files, err := ioutil.ReadDir(filepath.Join(home, contextsPath))
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	currentContext := viper.GetString(config.CurrentContext)
   277  	var contexts []*Info
   278  	for _, f := range files {
   279  		fullPath := getContextFilePath(f.Name())
   280  		ctxFile, err := config.NewContextFile(fullPath)
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  
   285  		isCurrent := false
   286  		name := strings.Replace(f.Name(), fileExtension, "", 1)
   287  		if currentContext == name {
   288  			isCurrent = true
   289  		}
   290  		c := NewInfo(name, isCurrent, ctxFile)
   291  		contexts = append(contexts, c)
   292  	}
   293  	return contexts, err
   294  }
   295  
   296  // getContextFilePath returns the full path to
   297  // the context file
   298  func getContextFilePath(name string) string {
   299  	home := config.GetHomeDir()
   300  	return filepath.Join(home, contextsPath, name)
   301  }
   302  
   303  func ValidateAPIURL(apiURL string) error {
   304  	if !strings.Contains(apiURL, "://") {
   305  		return errors.New("Invalid Fn API URL: does not contain ://")
   306  	}
   307  
   308  	_, err := url.Parse(apiURL)
   309  	if err != nil {
   310  		return fmt.Errorf("Invalid Fn API URL: %s", err)
   311  	}
   312  	return nil
   313  }
   314  
   315  func ValidateContextName(context string) error {
   316  	re := regexp.MustCompile("[^a-zA-Z0-9_-]+")
   317  
   318  	for range re.FindAllString(context, -1) {
   319  		return errors.New("Please enter a context name with only Alphanumeric, _, or -")
   320  	}
   321  	return nil
   322  }
   323  
   324  func (ctxMap *ContextMap) Set(key, value string) error {
   325  	contextFilePath := createFilePath(viper.GetString(config.CurrentContext) + fileExtension)
   326  	f, err := os.OpenFile(contextFilePath, os.O_RDWR, config.ReadWritePerms)
   327  	if err != nil {
   328  		return err
   329  	}
   330  	defer f.Close()
   331  
   332  	file, err := config.DecodeYAMLFile(f.Name())
   333  	if err != nil {
   334  		return err
   335  	}
   336  
   337  	if key == provider.CfgFnAPIURL {
   338  		err := ValidateAPIURL(value)
   339  		if err != nil {
   340  			return err
   341  		}
   342  	}
   343  
   344  	(*file)[key] = value
   345  	return config.WriteYamlFile(f.Name(), file)
   346  }
   347  
   348  func (ctxMap *ContextMap) UnSet(key string) error {
   349  	contextFilePath := createFilePath(viper.GetString(config.CurrentContext) + fileExtension)
   350  	f, err := os.OpenFile(contextFilePath, os.O_RDWR, config.ReadWritePerms)
   351  	if err != nil {
   352  		return err
   353  	}
   354  	defer f.Close()
   355  
   356  	file, err := config.DecodeYAMLFile(f.Name())
   357  	if err != nil {
   358  		return err
   359  	}
   360  
   361  	if _, ok := (*file)[key]; !ok {
   362  		return errors.New("Context file does not contain key: " + key)
   363  	}
   364  
   365  	delete((*file), key)
   366  	return config.WriteYamlFile(f.Name(), file)
   367  }