Google Cloud Golang: How to parse project/zone/instance from instance URL? - go

I'm using compute.NewRegionInstanceGroupManagersService's ListManagedInstances call which returns ManagedInstance's.
ManagedInstance has a field Instance which is an instance url, like https://www.googleapis.com/compute/v1/projects/my-project/zones/us-central1-b/instances/instance-group-z0hf
Now I would like to get more details about this particular instance. So using InstanceService's Get call, the function signature looks like this:
func (r *InstancesService) Get(project string, zone string, instance string) *InstancesGetCall
What's the best way to parse the instance URL (see above) into its project, zone and instance parts? Or is there a way of using another method to pass the instance URL directly?

you could do something like this,
parse the URL to get its path
split the path by slash component
iterate the parts,
locate static strings
take the next value and assign it appropriately.
package main
import (
"fmt"
"net/url"
"strings"
)
func main() {
s := "https://www.googleapis.com/compute/v1/projects/my-project/zones/us-central1-b/instances/instance-group-z0hf"
u, err := url.Parse(s)
if err != nil {
panic(err)
}
parts := strings.Split(u.Path, "/")
var project string
var zone string
var inst string
for i := 0; i < len(parts); i++ {
if parts[i] == "projects" && i+1 < len(parts) {
project = parts[i+1]
i++
} else if parts[i] == "zones" && i+1 < len(parts) {
zone = parts[i+1]
i++
} else if parts[i] == "instances" && i+1 < len(parts) {
inst = parts[i+1]
i++
}
}
fmt.Println(project, zone, inst)
}
//Ouptput:
//my-project us-central1-b instance-group-z0hf
Alternatively, use the route engine from gorilla to create a new pattern, apply the route to the url path and collect output results. But it is more complex and probably not justified.

URLs are complex animals, the best way is using the library url.Parse. Then you can use a regex or split to extract the data you need from the path part.

Related

How to get the index of a string in an array?

Here is my sample code:
slice_of_string := strings.Split("root/alpha/belta", "/")
res1 := bytes.IndexAny(slice_of_string , "alpha")
I got this error
./prog.go:16:24: cannot use a (type []string) as type []byte in argument to bytes.IndexAny
The logic here is when I input a path and a folder name (or file name), I want to know the level of the folder name (or a file name) in that path.
I do it by:
Split the path to an array
Get the index of the folder name (or a file name) in the path
If the index is 0 then the level would be 1, etc.
You probably need to loop over the slice and find the element that you are looking for.
func main() {
path := "root/alpha/belta"
key := "alpha"
index := getIndexInPath(path, key)
fmt.Println(index)
}
func getIndexInPath(path string, key string) int {
parts := strings.Split(path, "/")
if len(parts) > 0 {
for i := len(parts) - 1; i >= 0; i-- {
if parts[i] == key {
return i
}
}
}
return -1
}
Note that the loop is backwards to address the logic issue that Burak Serdar pointed out that it may fail on a path like /a/a/a/a otherwise.
Use strings.IndexAny instead of bytes.IndexAny if you want to operate on a []string.
there are no inbuild function available in standard library to search in slice of string, but if slice of string is sorted then you can use sort.SearchStrings to search. But in case of unsorted slice of string you have to implement it using for loop.

Map seems to drop values in recursion

I've been working on a problem and I figured I would demonstrate it using a pokemon setup. I am reading from a file, parsing the file and creating objects/structs from them. This normally isn't a problem except now I need to implement interface like inheriting of traits. I don't want there to be duplicate skills in there so I figured I could use a map to replicate a set data structure. However it seems that in the transitive phase of my recursive parsePokemonFile function (see the implementsComponent case), I appear to be losing values in my map.
I am using the inputs like such:
4 files
Ratatta:
name=Ratatta
skills=Tackle:normal,Scratch:normal
Bulbosaur:
name=Bulbosaur
implements=Ratatta
skills=VineWhip:leaf
Oddish:
name=Oddish
implements=Ratatatt
skills=Acid:poison
Venosaur:
name=Venosaur
implements=bulbosaur,oddish
I'm expecting the output for the following code to be something like
Begin!
{Venosaur [{VineWhip leaf} {Acid poison} {Tackle normal} {Scratch normal}]}
but instead I get
Begin!
{Venosaur [{VineWhip leaf} {Acid poison}]}
What am I doing wrong? Could it be a logic error? Or am I making an assumption about the map holding values that I shouldn't?
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
// In order to create a set of pokemon abilities and for ease of creation and lack of space being taken up
// We create an interfacer capability that imports the skills and attacks from pokemon of their previous evolution
// This reduces the amount of typing of skills we have to do.
// Algorithm is simple. Look for the name "implements=x" and then add x into set.
// Unfortunately it appears that the set is dropping values on transitive implements interfaces
func main() {
fmt.Println("Begin!")
dex, err := parsePokemonFile("Venosaur")
if err != nil {
fmt.Printf("Got error: %v\n", err)
}
fmt.Printf("%v\n", dex)
}
type pokemon struct {
Name string
Skills []skill
}
type skill struct {
SkillName string
Type string
}
func parsePokemonFile(filename string) (pokemon, error) {
file, err := os.Open(filename)
if err != nil {
return pokemon{}, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var builtPokemon pokemon
for scanner.Scan() {
component, returned := parseLine(scanner.Text())
switch component {
case nameComponent:
builtPokemon.Name = returned
case skillsComponent:
skillsStrings := strings.Split(returned, ",")
var skillsArr []skill
// split skills and add them into pokemon skillset
for _, skillStr := range skillsStrings {
skillPair := strings.Split(skillStr, ":")
skillsArr = append(skillsArr, skill{SkillName: skillPair[0], Type: skillPair[1]})
}
builtPokemon.Skills = append(builtPokemon.Skills, skillsArr...)
case implementsComponent:
implementsArr := strings.Split(returned, ",")
// create set to remove duplicates
skillsSet := make(map[*skill]bool)
for _, val := range implementsArr {
// recursively call the pokemon files and get full pokemon
implementedPokemon, err := parsePokemonFile(val)
if err != nil {
return pokemon{}, err
}
// sieve out the skills into a set
for _, skill := range implementedPokemon.Skills {
skillsSet[&skill] = true
}
}
// append final set into the currently being built pokemon
for x := range skillsSet {
builtPokemon.Skills = append(builtPokemon.Skills, *x)
}
}
}
return builtPokemon, nil
}
type component int
// components to denote where to put our strings when it comes time to assemble what we've parsed
const (
nameComponent component = iota
implementsComponent
skillsComponent
)
func parseLine(line string) (component, string) {
arr := strings.Split(line, "=")
switch arr[0] {
case "name":
return nameComponent, arr[1]
case "implements":
return implementsComponent, arr[1]
case "skills":
return skillsComponent, arr[1]
default:
panic("Invalid field found")
}
}
This has nothing to do with Golang maps dropping any values.
The problem is that you are using a map of skill pointers and not skills. Two pointers to the same skill content can be different.
skillsSet := make(map[*skill]bool)
If you change this to map[skill]bool, this should work. You may try it out!

Using default value in golang func

I'm trying to implement a default value according to the option 1 of the post Golang and default values. But when I try to do go install the following error pops up in the terminal:
not enough arguments in call to test.Concat1
have ()
want (string)
Code:
package test
func Concat1(a string) string {
if a == "" {
a = "default-a"
}
return fmt.Sprintf("%s", a)
}
// other package
package main
func main() {
test.Concat1()
}
Thanks in advance.
I don't think what you are trying to do will work that way. You may want to opt for option #4 from the page you cited, which uses variadic variables. In your case looks to me like you want just a string, so it'd be something like this:
func Concat1(a ...string) string {
if len(a) == 0 {
return "a-default"
}
return a[0]
}
Go does not have optional defaults for function arguments.
You may emulate them to some extent by having a special type
to contain the set of parameters for a function.
In your toy example that would be something like
type Concat1Args struct {
a string
}
func Concat1(args Concat1Args) string {
if args.a == "" {
args.a = "default-a"
}
return fmt.Sprintf("%s", args.a)
}
The "trick" here is that in Go each type has its respective
"zero value", and when producing a value of a composite type
using the so-called literal, it's possible to initialize only some of the type's fields, so in our example that would be
s := Concat1(Concat1Args{})
vs
s := Concat1(Concat1Args{"whatever"})
I know that looks clumsy, and I have showed this mostly for
demonstration purpose. In real production code, where a function
might have a dozen of parameters or more, having them packed
in a dedicate composite type is usually the only sensible way
to go but for a case like yours it's better to just explicitly
pass "" to the function.
Golang does not support default parameters. Accordingly, variadic arguments by themselves are not analogous. However, variadic functions with the use of error handling can 'resemble' the pattern. Try the following as a simple example:
package main
import (
"errors"
"log"
)
func createSeries(p ...int) ([]int, error) {
usage := "Usage: createSeries(<length>, <optional starting value>), length should be > 0"
if len(p) == 0 {
return nil, errors.New(usage)
}
n := p[0]
if n <= 0 {
return nil, errors.New(usage)
}
var base int
if len(p) == 2 {
base = p[1]
} else if len(p) > 2 {
return nil, errors.New(usage)
}
vals := make([]int, n)
for i := 0; i < n; i++ {
vals[i] = base + i
}
return vals, nil
}
func main() {
answer, err := createSeries(4, -9)
if err != nil {
log.Fatal(err)
}
log.Println(answer)
}
Default parameters work differently in Go than they do in other languages. In a function there can be one ellipsis, always at the end, which will keep a slice of values of the same type so in your case this would be:
func Concat1(a ...string) string {
but that means that the caller may pass in any number of arguments >= 0. Also you need to check that the arguments in the slice are not empty and then assign them yourself. This means they do not get assigned a default value through any kind of special syntax in Go. This is not possible but you can do
if a[0] == "" {
a[0] = "default value"
}
If you want to make sure that the user passes either zero or one strings, just create two functions in your API, e.g.
func Concat(a string) string { // ...
func ConcatDefault() string {
Concat("default value")
}

Does go provide variable sanitization?

I am a beginner in Golang.
I have a problem with variable type assigning from user input.
When the user enters data like "2012BV352" I need to be able to ignore the BV and pass 2012352 to my next function.
There has a package name gopkg.in/validator.v2 in doc
But what it returns is whether or not the variable is safe or not.
I need to cut off the unusual things.
Any idea on how to achieve this?
You could write your own sanitizing methods and if it becomes something you'll be using more often, I'd package it out and add other methods to cover more use cases.
I provide two different ways to achieve the same result. One is commented out.
I haven't run any benchmarks so i couldn't tell you for certain which is more performant, but you could write your own tests if you wanted to figure it out. It would also expose another important aspect of Go and in my opinion one of it's more powerful tools... testing.
package main
import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
)
// using a regex here which simply targets all digits and ignores everything else. I make it a global var and use MustCompile because the
// regex doesn't need to be created every time.
var extractInts = regexp.MustCompile(`\d+`)
func SanitizeStringToInt(input string) (int, error) {
m := extractInts.FindAllString(input, -1)
s := strings.Join(m, "")
return strconv.Atoi(s)
}
/*
// if you didn't want to use regex you could use a for loop
func SanitizeStringToInt(input string) (int, error) {
var s string
for _, r := range input {
if !unicode.IsLetter(r) {
s += string(r)
}
}
return strconv.Atoi(s)
}
*/
func main() {
a := "2012BV352"
n, err := SanitizeStringToInt(a)
if err != nil {
log.Fatal(err)
}
fmt.Println(n)
}

How do I parse URLs in the format of /id/123 not ?foo=bar

I'm trying to parse an URL like:
http://example.com/id/123
I've read through the net/url docs but it seems like it only parses strings like
http://example.com/blah?id=123
How can I parse the ID so I end up with the value of the id in the first example?
This is not one of my own routes but a http string returned from an openid request.
In your example /id/123 is a path and you can get the "123" part by using Base from the path module.
package main
import (
"fmt"
"path"
)
func main() {
fmt.Println(path.Base("/id/123"))
}
For easy reference, here's the docs on the path module. http://golang.org/pkg/path/#example_Base
You can try using regular expression as follow:
import "regexp"
re, _ := regexp.Compile("/id/(.*)")
values := re.FindStringSubmatch(path)
if len(values) > 0 {
fmt.Println("ID : ", values[1])
}
Here is a simple solution that works for URLs with the same structure as yours (you can improve to suit those with other structures)
package main
import (
"fmt"
"net/url"
)
var path = "http://localhost:8080/id/123"
func getFirstParam(path string) (ps string) {
// ignore first '/' and when it hits the second '/'
// get whatever is after it as a parameter
for i := 1; i < len(path); i++ {
if path[i] == '/' {
ps = path[i+1:]
}
}
return
}
func main() {
u, _ := url.Parse(path)
fmt.Println(u.Path) // -> "/id/123"
fmt.Println(getFirstParam(u.Path)) // -> "123"
}
Or, as #gollipher suggested, use the path package
import "path"
func main() {
u, _ := url.Parse(path)
ps := path.Base(u.Path)
}
With this method it's faster than regex, provided you know before hand the structure of the URL you are getting.

Resources