Variables in the HCL file cannot be parsed - file

I have a Go project where I want to read a HCL file. This HCL file contains variables. However, I cannot parse it and I get the following error message:
Variables not allowed; Variables may not be used here., and 1 other diagnostic(s)
My Go Code:
package main
import (
"fmt"
"log"
"github.com/hashicorp/hcl/v2/hclsimple"
)
type Config struct {
Hello string `hcl:"hello"`
World string `hcl:"world"`
Message string `hcl:"message"`
}
func main() {
var config Config
err := hclsimple.DecodeFile("test.hcl", nil, &config)
if err != nil {
log.Fatalf("Failed to load configuration: %s", err)
}
fmt.Println(config.Message)
}
My HCL File
hello = "hello"
world = "world"
message = "hello ${world}"
What am I doing wrong? Is my HCL syntax perhaps not correct?

Is my HCL syntax perhaps not correct?
It's syntactically valid but doesn't work the way you're expecting it to. HCL doesn't allow for referencing arbitrary values defined elsewhere in the HCL file. It allows only for referencing variables which are exposed by the parser. For example, this gives the expected output:
ectx := &hcl.EvalContext{Variables: map[string]cty.Value{"world": cty.StringVal("world")}}
err := hclsimple.DecodeFile("test.hcl", ectx, &config)
The documentation doesn't make this especially clear, but the relevant reference would be here: https://github.com/hashicorp/hcl/blob/main/guide/go_expression_eval.rst#expression-evaluation-modes

Related

How to check file `name` (with any extension) does exist in the directory in Golang?

I know I could check the file does exist or not in Golang by the answers of the following questions.
How to check whether a file or directory denoted by a path exists in Golang?
How to check if a file exists in Go?
Gist - Check if file or directory exists in Golang
The code looks like this.
_, err := os.Stat(path)
if err == nil {
log.Printf("File %s exists.", path)
} else if os.IsNotExist(err) {
log.Printf("File %s not exists.", path)
} else {
log.Printf("File %s stat error: %v", path, err)
}
But here's my real question, how do I check the filename does exist (has been used) in the specified directory? For example if I have a file tree like this:
--- uploads
|- foo.png
|- bar.mp4
I wanted to check if there's any file is using the specified name..
used := filenameUsed("uploads/foo")
fmt.Println(used) // Output: true
used = filenameUsed("uploads/hello")
fmt.Println(used) // Output: false
How do I implement the filenameUsed function?
Google gave me a path/filepath package as the result but I have no clue about how to use it.
You may use the filepath.Glob() function where you can specify a pattern to list files.
The pattern to be used is basically the name you wish to check if used, extended with the any extension pattern.
Example:
func filenameUsed(name string) (bool, error) {
matches, err := filepath.Glob(name + ".*")
if err != nil {
return false, err
}
return len(matches) > 0, nil
}
Using / testing it:
fmt.Print("Filename foo used: ")
fmt.Println(filenameUsed("uploads/foo"))
fmt.Print("Filename bar used: ")
fmt.Println(filenameUsed("uploads/bar"))
Example output:
Filename foo used: true <nil>
Filename bar used: false <nil>
However, note that filenameUsed() returning false (and nil error) does not mean a file with that name won't exist if you attempt to create one after. Meaning checking it and attempting to create such a file does not guarantee atomicity. If your purpose is to create a file if the name is not used, then simply try to create the file in the proper mode (do not overwrite if exists), and handle the (creation) error returned by that call.

Does the error returned by db.Exec(...) have a code?

I'm trying to delete a database using the postgres driver (lib/pq) by doing a:
db.Exec("DROP DATABASE dbName;")
But I'd like to do a different conditional based on whether the error received is something strange, or is a "database does not exist" error.
Is there a constant variable or something I can use to check if the error returned is a "database does not exist" error message, or would I have to manually parse the error string myself?
I tried to look in the documentation, but could not find anything for "database does not exist". I did however find this list.
Perhaps it fits under some other error code? Also I'm not quite sure the semantically correct way of fetching and comparing the error codes through the Postgres driver. I presume I should do something like this:
if err.ErrorCode != "xxx"
The lib/pq package may return errors of type *pq.Error, which is a struct. If it does, you may use all its fields to inspect for details of the error.
This is how it can be done:
if err, ok := err.(*pq.Error); ok {
// Here err is of type *pq.Error, you may inspect all its fields, e.g.:
fmt.Println("pq error:", err.Code.Name())
}
pq.Error has the following fields:
type Error struct {
Severity string
Code ErrorCode
Message string
Detail string
Hint string
Position string
InternalPosition string
InternalQuery string
Where string
Schema string
Table string
Column string
DataTypeName string
Constraint string
File string
Line string
Routine string
}
The meaning and possible values of these fields are Postres specific and the full list can be found here: Error and Notice Message Fields
You could use this: https://github.com/omeid/pgerror
It has lots of mappings for various postgres errors.
With the package, you can do the following (taken from the README):
// example use:
_, err = stmt.Exec(SomeInsertStateMent, params...)
if err != nil {
if e := pgerror.UniqueViolation(err); e != nil {
// you can use e here to check the fields et al
return SomeThingAlreadyExists
}
return err // other cases.
}
This package has all the PG error constants:
https://github.com/jackc/pgerrcode
Just import and you're good to go:
import "github.com/jackc/pgerrcode"
// ...
if err, ok := err.(*pq.Error); ok {
if err.Code == pgerrcode.UniqueViolation {
return fmt.Errorf("unique field violation on column %s", err.Column)
}
}
The package is also in the family of one of the 2 or 3 most popular Go PostgreSQL drivers, called "pgx", so it should be reliable enough.

How to write a safe rename in Go? (Or, how to write this Python in Go?)

I've got the following code in Python:
if not os.path.exists(src): sys.exit("Does not exist: %s" % src)
if os.path.exists(dst): sys.exit("Already exists: %s" % dst)
os.rename(src, dst)
From this question, I understand that there is no direct method to test if a file exists or doesn't exist.
What is the proper way to write the above in Go, including printing out the correct error strings?
Here is the closest I've gotten:
package main
import "fmt"
import "os"
func main() {
src := "a"
dst := "b"
e := os.Rename(src, dst)
if e != nil {
fmt.Println(e.(*os.LinkError).Op)
fmt.Println(e.(*os.LinkError).Old)
fmt.Println(e.(*os.LinkError).New)
fmt.Println(e.(*os.LinkError).Err)
}
}
From the availability of information from the error, where it effectively doesn't tell you what the problem is without you parsing an English freeformat string, it seems to me that it is not possible to write the equivalent in Go.
The code you provide contains a race condition: Between you checking for dst to not exist and copying something into dst, a third party could have created the file dst, causing you to overwrite a file. Either remove the os.path.exists(dst) check because it cannot reliably detect if the target exists at the time you try to remove it, or employ the following algorithm instead:
Create a hardlink from src to dst. If a file named dst exists, the operation will fail and you can bail out. If src does not exist, the operation will fail, too.
Remove src.
The following code implements the two-step algorithm outlined above in Go.
import "os"
func renameAndCheck(src, dst string) error {
err := os.Link(src, dst)
if err != nil {
return err
}
return os.Remove(src)
}
You can check for which reason the call to os.Link() failed:
If the error satisfies os.IsNotExist(), the call failed because src did not exist at the time os.Link() was called
If the error satisfies os.IsExist(), the call failed because dst exists at the time os.Link() is called
If the error satisfies os.IsPermission(), the call failed because you don't have sufficient permissions to create a hard link
As far as I know, other reasons (like the file system not supporting the creation of hard links or src and dst being on different file systems) cannot be tested portably.
The translation if your Python code to Go is:
if _, err := os.Stat(src); err != nil {
// The source does not exist or some other error accessing the source
log.Fatal("source:", err)
}
if _, err := os.Stat(dst); !os.IsNotExists(dst) {
// The destination exists or some other error accessing the destination
log.Fatal("dest:", err)
}
if err := os.Rename(src, dst); err != nil {
log.Fatal(err)
}
The three function call sequence is not safe (I am referring to both the original Python version and my replication of it here). The source can be removed or the destination can be created after the checks, but before the rename.
The safe way to move a file is OS dependent. On Windows, you can just call os.Rename(). On Windows, this function will fail if the destination exists or the source does not. On Posix systems, you should link and remove as described in another answer.

How to Unmarshal the json array?

There is something wrong when I unmarshal the json array.
How do I correct it ? the code is:http://play.golang.org/p/AtU9q8Hlye
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string
ServerIP string
}
type Serverslice struct {
Name string
Servers []Server
}
func main() {
var s []Serverslice
str := `{"name":"dxh","servers":[{"serverName":"VPN0","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}],
"name":"dxh1,"servers":[{"serverName":"VPN1","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`
json.Unmarshal([]byte(str), &s) //the wrong line.....................
fmt.Println(len(s))
}
First of all, you're ignoring the error return value from json.Unmarshal. You probably want something like:
if err := json.Unmarshal([]byte(str), &s); err != nil {
log.Fatalln(err)
}
With that change, we can see that your JSON data isn't valid: invalid character 's' after object key:value pair. There is a missing quote at the end of "dxh1 on the second line.
Fixing that error and rerunning the program you'll get a different error: json: cannot unmarshal object into Go value of type []main.Serverslice. There are two possible problems here:
You meant to decode into an object. In this case, just declare s as a Serverslice. Here is a version of your program that makes that change: http://play.golang.org/p/zgyr_vnn-_
Your JSON is supposed to be an array (possible, since it seems to have duplicate keys). Here's an updated version with the JSON changed to provide an array: http://play.golang.org/p/Wl6kUaivEm

_file_ or _line_ similar in golang

is there any function in go that is similar like "_file_" or "_line_" in go, to know who is calling a specific function during run time? In C we have the "_file_" line that can be called as macros. How to do this in go?
If you're using the log package, you can instruct the logger to prefix the entries with various information. You'll likely be most interested in the Lshortfile constant, which will result in prefixes along the lines of d.go:23. Alternatively, there is Llongfile which prints the file's full path (such as /a/b/c/d.go:23).
If you don't want to use the log package, you could also use runtime.Caller(), which is what the log package uses internally. It's not as straight forward as the C macros, but you could hide it behind a function (and specify the correct call depth). You can see how the log package is implemented for an example (line 140).
(1) Write a brief function that calls runtime.Caller()
(2) Call that function everywhere you want to access the source code file and line number at run time.
Example:
import "runtime"
func file_line() string {
_, fileName, fileLine, ok := runtime.Caller(1)
var s string
if ok {
s = fmt.Sprintf("%s:%d", fileName, fileLine)
} else {
s = ""
}
return s
}
Note: pass 1 to Caller() so that it returns the number of the line where file_line() is called, instead of where runtime.Caller() is called.
fmt.Println(file_line()) // Prints this file and line number.
See the runtime and runtime.debug packages and in particular the Stack, PrintStack or Callerfunctions.
Stack formats a stack trace of the calling goroutine into buf and returns the number of bytes written to buf. If all is true, Stack formats stack traces of all other goroutines into buf after the trace for the current goroutine.
If you are compiling with debug information, then this will should contain the line number in source
//import ( "log" "strings" "bytes" "path/filepath" )
func SRC_FILE() string {
var buf bytes.Buffer
log.New(&buf, "", log.Lshortfile).Output(2, "")//2=>CALLER
__FILE_LINE__ := strings.TrimSpace(buf.String())
__FILE__ := strings.TrimRight(__FILE_LINE__, ":1234567890")
return __FILE__
}
func SRC_DIR() string {
var buf bytes.Buffer
log.New(&buf, "", log.Llongfile).Output(2, "")//2=>CALLER
__FILE_LINE__ := strings.TrimSpace(buf.String())
__FILE__ := strings.TrimRight(__FILE_LINE__, ":1234567890")
__DIR__ := filepath.Dir(__FILE__)
return __DIR__
}
func main() {
fmt.Println(SRC_FILE())//analogous to __FILE__
fmt.Println(SRC_DIR()) //analogous to __DIR__
}
https://go.dev/play/p/3nvgknX96RO
When you run it on go.dev/play your code is run as prog.go

Resources