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 }