Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a398ce82f7 | ||
|
e814b37839 | ||
|
cb4d9e81a4 | ||
|
b87a4628e6 | ||
|
b22fdd5ac9 | ||
|
6939fc2ddc | ||
|
e3117269cb | ||
|
3bb3a683a4 | ||
|
e1033c979e | ||
|
9a4d24826f | ||
|
7bed1307e1 | ||
|
47b536532d | ||
|
7df5cf761e | ||
|
799c02865c | ||
|
9f38792d43 | ||
|
7e4fa423e4 | ||
|
c3f17bd07b | ||
|
85a473d972 | ||
|
aea5ca5252 | ||
|
4e84180ad5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
*.swp
|
||||
bin/
|
||||
coverage/
|
||||
pkg/
|
||||
|
37
Documentation/cloud-config-oem.md
Normal file
37
Documentation/cloud-config-oem.md
Normal file
@@ -0,0 +1,37 @@
|
||||
## OEM configuration
|
||||
|
||||
The `coreos.oem.*` parameters follow the [os-release spec][os-release], but have been repurposed as a way for coreos-cloudinit to know about the OEM partition on this machine. Customizing this section is only needed when generating a new OEM of CoreOS from the SDK. The fields include:
|
||||
|
||||
- **id**: Lowercase string identifying the OEM
|
||||
- **name**: Human-friendly string representing the OEM
|
||||
- **version-id**: Lowercase string identifying the version of the OEM
|
||||
- **home-url**: Link to the homepage of the provider or OEM
|
||||
- **bug-report-url**: Link to a place to file bug reports about this OEM
|
||||
|
||||
coreos-cloudinit renders these fields to `/etc/oem-release`.
|
||||
If no **id** field is provided, coreos-cloudinit will ignore this section.
|
||||
|
||||
For example, the following cloud-config document...
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
coreos:
|
||||
oem:
|
||||
id: rackspace
|
||||
name: Rackspace Cloud Servers
|
||||
version-id: 168.0.0
|
||||
home-url: https://www.rackspace.com/cloud/servers/
|
||||
bug-report-url: https://github.com/coreos/coreos-overlay
|
||||
```
|
||||
|
||||
...would be rendered to the following `/etc/oem-release`:
|
||||
|
||||
```
|
||||
ID=rackspace
|
||||
NAME="Rackspace Cloud Servers"
|
||||
VERSION_ID=168.0.0
|
||||
HOME_URL="https://www.rackspace.com/cloud/servers/"
|
||||
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
|
||||
```
|
||||
|
||||
[os-release]: http://www.freedesktop.org/software/systemd/man/os-release.html
|
@@ -70,44 +70,23 @@ Note that hyphens in the coreos.etcd.* keys are mapped to underscores.
|
||||
|
||||
[etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
|
||||
|
||||
#### oem
|
||||
#### update
|
||||
|
||||
The `coreos.oem.*` parameters follow the [os-release spec][os-release], but have been repurposed as a way for coreos-cloudinit to know about the OEM partition on this machine:
|
||||
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
|
||||
|
||||
- **id**: Lowercase string identifying the OEM
|
||||
- **name**: Human-friendly string representing the OEM
|
||||
- **version-id**: Lowercase string identifying the version of the OEM
|
||||
- **home-url**: Link to the homepage of the provider or OEM
|
||||
- **bug-report-url**: Link to a place to file bug reports about this OEM
|
||||
|
||||
coreos-cloudinit renders these fields to `/etc/oem-release`.
|
||||
If no **id** field is provided, coreos-cloudinit will ignore this section.
|
||||
|
||||
For example, the following cloud-config document...
|
||||
- **reboot-strategy**: One of "reboot", "etcd-lock", "best-effort" or "off" for controlling when reboots are issued after an update is performed.
|
||||
- _reboot_: Reboot immediately after an update is applied.
|
||||
- _etcd-lock_: Reboot after first taking a distributed lock in etcd, this guarantees that only one host will reboot concurrently and that the cluster will remain available during the update.
|
||||
- _best-effort_ - If etcd is running, "etcd-lock", otherwise simply "reboot".
|
||||
- _off_ - Disable rebooting after updates are applied (not recommended).
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
coreos:
|
||||
oem:
|
||||
id: rackspace
|
||||
name: Rackspace Cloud Servers
|
||||
version-id: 168.0.0
|
||||
home-url: https://www.rackspace.com/cloud/servers/
|
||||
bug-report-url: https://github.com/coreos/coreos-overlay
|
||||
update:
|
||||
reboot-strategy: etcd-lock
|
||||
```
|
||||
|
||||
...would be rendered to the following `/etc/oem-release`:
|
||||
|
||||
```
|
||||
ID=rackspace
|
||||
NAME="Rackspace Cloud Servers"
|
||||
VERSION_ID=168.0.0
|
||||
HOME_URL="https://www.rackspace.com/cloud/servers/"
|
||||
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
|
||||
```
|
||||
|
||||
[os-release]: http://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
|
||||
#### units
|
||||
|
||||
The `coreos.units.*` parameters define a list of arbitrary systemd units to start. Each item is an object with the following fields:
|
||||
|
5
build
5
build
@@ -1,6 +1,9 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
ORG_PATH="github.com/coreos"
|
||||
REPO_PATH="${ORG_PATH}/coreos-cloudinit"
|
||||
|
||||
export GOBIN=${PWD}/bin
|
||||
export GOPATH=${PWD}
|
||||
|
||||
go build -o bin/coreos-cloudinit github.com/coreos/coreos-cloudinit
|
||||
go build -o bin/coreos-cloudinit ${REPO_PATH}
|
||||
|
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
const version = "0.5.2"
|
||||
const version = "0.6.1"
|
||||
|
||||
func main() {
|
||||
var printVersion bool
|
||||
@@ -42,11 +42,6 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if file != "" && url != "" && !useProcCmdline {
|
||||
fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var ds datasource.Datasource
|
||||
if file != "" {
|
||||
ds = datasource.NewLocalFile(file)
|
||||
|
27
cover
Executable file
27
cover
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Generate coverage HTML for a package
|
||||
# e.g. PKG=./initialize ./cover
|
||||
#
|
||||
|
||||
if [ -z "$PKG" ]; then
|
||||
echo "cover only works with a single package, sorry"
|
||||
exit 255
|
||||
fi
|
||||
|
||||
COVEROUT="coverage"
|
||||
|
||||
if ! [ -d "$COVEROUT" ]; then
|
||||
mkdir "$COVEROUT"
|
||||
fi
|
||||
|
||||
# strip out slashes and dots
|
||||
COVERPKG=${PKG//\//}
|
||||
COVERPKG=${COVERPKG//./}
|
||||
|
||||
# generate arg for "go test"
|
||||
export COVER="-coverprofile ${COVEROUT}/${COVERPKG}.out"
|
||||
|
||||
source ./test
|
||||
|
||||
go tool cover -html=${COVEROUT}/${COVERPKG}.out
|
@@ -14,6 +14,7 @@ type CloudConfig struct {
|
||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||
Coreos struct {
|
||||
Etcd EtcdEnvironment
|
||||
Update map[string]string
|
||||
Units []system.Unit
|
||||
OEM OEMRelease
|
||||
}
|
||||
@@ -128,6 +129,13 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
||||
log.Printf("Wrote etcd config file to filesystem")
|
||||
}
|
||||
|
||||
if s, ok := cfg.Coreos.Update["reboot-strategy"]; ok {
|
||||
if err := WriteLocksmithConfig(s, env.Root()); err != nil {
|
||||
log.Fatalf("Failed to write locksmith config to filesystem: %v", err)
|
||||
}
|
||||
log.Printf("Wrote locksmith config file to filesystem")
|
||||
}
|
||||
|
||||
if len(cfg.Coreos.Units) > 0 {
|
||||
commands := make(map[string]string, 0)
|
||||
for _, unit := range cfg.Coreos.Units {
|
||||
|
@@ -32,6 +32,8 @@ func TestCloudConfig(t *testing.T) {
|
||||
coreos:
|
||||
etcd:
|
||||
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
||||
update:
|
||||
reboot-strategy: reboot
|
||||
units:
|
||||
- name: 50-eth0.network
|
||||
runtime: yes
|
||||
@@ -129,6 +131,9 @@ Address=10.209.171.177/19
|
||||
if cfg.Hostname != "trontastic" {
|
||||
t.Errorf("Failed to parse hostname")
|
||||
}
|
||||
if cfg.Coreos.Update["reboot-strategy"] != "reboot" {
|
||||
t.Errorf("Failed to parse locksmith strategy")
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that our interface conversion doesn't panic
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -68,7 +67,7 @@ func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
if err := WriteEtcdEnvironment(ec, dir); err != nil {
|
||||
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
||||
@@ -106,7 +105,7 @@ func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
os.Mkdir(path.Join(dir, "etc"), os.FileMode(0755))
|
||||
err = ioutil.WriteFile(path.Join(dir, "etc", "machine-id"), []byte("node007"), os.FileMode(0444))
|
||||
|
85
initialize/locksmith.go
Normal file
85
initialize/locksmith.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
const locksmithUnit = "locksmithd.service"
|
||||
|
||||
// addStrategy creates an `/etc/coreos/update.conf` file with the requested
|
||||
// strategy via rewriting the file on disk or by starting from
|
||||
// `/usr/share/coreos/update.conf`.
|
||||
func addStrategy(strategy string, root string) error {
|
||||
etcUpdate := path.Join(root, "etc", "coreos", "update.conf")
|
||||
usrUpdate := path.Join(root, "usr", "share", "coreos", "update.conf")
|
||||
|
||||
// Ensure /etc/coreos/ exists before attempting to write a file in it
|
||||
os.MkdirAll(path.Join(root, "etc", "coreos"), 0755)
|
||||
|
||||
tmp, err := ioutil.TempFile(path.Join(root, "etc", "coreos"), ".update.conf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tmp.Chmod(0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf, err := os.Open(etcUpdate)
|
||||
if os.IsNotExist(err) {
|
||||
conf, err = os.Open(usrUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(conf)
|
||||
|
||||
sawStrat := false
|
||||
stratLine := "REBOOT_STRATEGY="+strategy
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "REBOOT_STRATEGY=") {
|
||||
line = stratLine
|
||||
sawStrat = true
|
||||
}
|
||||
fmt.Fprintln(tmp, line)
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !sawStrat {
|
||||
fmt.Fprintln(tmp, stratLine)
|
||||
}
|
||||
|
||||
return os.Rename(tmp.Name(), etcUpdate)
|
||||
}
|
||||
|
||||
// WriteLocksmithConfig updates the `update.conf` file with a REBOOT_STRATEGY for locksmith.
|
||||
func WriteLocksmithConfig(strategy string, root string) error {
|
||||
cmd := "restart"
|
||||
if strategy == "off" {
|
||||
err := system.MaskUnit(locksmithUnit, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = "stop"
|
||||
} else {
|
||||
return addStrategy(strategy, root)
|
||||
}
|
||||
if err := system.DaemonReload(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := system.RunUnitCommand(cmd, locksmithUnit); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
91
initialize/locksmith_test.go
Normal file
91
initialize/locksmith_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
base = `SERVER=https://example.com
|
||||
GROUP=thegroupc`
|
||||
|
||||
configured = base + `
|
||||
REBOOT_STRATEGY=awesome
|
||||
`
|
||||
|
||||
expected = base + `
|
||||
REBOOT_STRATEGY=etcd-lock
|
||||
`
|
||||
)
|
||||
|
||||
func setupFixtures(dir string) {
|
||||
os.MkdirAll(path.Join(dir, "usr", "share", "coreos"), 0755)
|
||||
os.MkdirAll(path.Join(dir, "run", "systemd", "system"), 0755)
|
||||
|
||||
ioutil.WriteFile(path.Join(dir, "usr", "share", "coreos", "update.conf"), []byte(base), 0644)
|
||||
}
|
||||
|
||||
func TestLocksmithEnvironmentWrittenToDisk(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
setupFixtures(dir)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
if i == 1 {
|
||||
err = ioutil.WriteFile(path.Join(dir, "etc", "coreos", "update.conf"), []byte(configured), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := WriteLocksmithConfig("etcd-lock", dir); err != nil {
|
||||
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
|
||||
}
|
||||
|
||||
fullPath := path.Join(dir, "etc", "coreos", "update.conf")
|
||||
|
||||
fi, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to stat file: %v", err)
|
||||
}
|
||||
|
||||
if fi.Mode() != os.FileMode(0644) {
|
||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read expected file: %v", err)
|
||||
}
|
||||
|
||||
if string(contents) != expected {
|
||||
t.Fatalf("File has incorrect contents, got %v, wanted %v", string(contents), expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestLocksmithEnvironmentMasked(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
setupFixtures(dir)
|
||||
|
||||
if err := WriteLocksmithConfig("off", dir); err != nil {
|
||||
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
|
||||
}
|
||||
|
||||
fullPath := path.Join(dir, "etc", "systemd", "system", "locksmithd.service")
|
||||
target, err := os.Readlink(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read link %v", err)
|
||||
}
|
||||
if target != "/dev/null" {
|
||||
t.Fatalf("Locksmith not masked, unit target %v", target)
|
||||
}
|
||||
}
|
@@ -4,7 +4,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -20,7 +19,7 @@ func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
if err := WriteOEMRelease(&oem, dir); err != nil {
|
||||
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
||||
|
@@ -31,7 +31,6 @@ func (f *File) Permissions() (os.FileMode, error) {
|
||||
return os.FileMode(perm), nil
|
||||
}
|
||||
|
||||
|
||||
func WriteFile(f *File) error {
|
||||
if f.Encoding != "" {
|
||||
return fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
|
||||
|
@@ -165,3 +165,11 @@ func MachineID(root string) string {
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func MaskUnit(unit string, root string) error {
|
||||
masked := path.Join(root, "etc", "systemd", "system", unit)
|
||||
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Symlink("/dev/null", masked)
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -24,7 +23,7 @@ Address=10.209.171.177/19
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
dst := UnitDestination(&u, dir)
|
||||
expectDst := path.Join(dir, "run", "systemd", "network", "50-eth0.network")
|
||||
@@ -75,7 +74,7 @@ Where=/media/state
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
dst := UnitDestination(&u, dir)
|
||||
expectDst := path.Join(dir, "etc", "systemd", "system", "media-state.mount")
|
||||
@@ -115,7 +114,7 @@ func TestMachineID(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer syscall.Rmdir(dir)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
os.Mkdir(path.Join(dir, "etc"), os.FileMode(0755))
|
||||
ioutil.WriteFile(path.Join(dir, "etc", "machine-id"), []byte("node007\n"), os.FileMode(0444))
|
||||
@@ -124,3 +123,22 @@ func TestMachineID(t *testing.T) {
|
||||
t.Fatalf("File has incorrect contents")
|
||||
}
|
||||
}
|
||||
func TestMaskUnit(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
if err := MaskUnit("foo.service", dir); err != nil {
|
||||
t.Fatalf("Unable to mask unit: %v", err)
|
||||
}
|
||||
|
||||
fullPath := path.Join(dir, "etc", "systemd", "system", "foo.service")
|
||||
target, err := os.Readlink(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read link", err)
|
||||
}
|
||||
if target != "/dev/null" {
|
||||
t.Fatalf("unit not masked, got unit target", target)
|
||||
}
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ func CreateUser(u *User) error {
|
||||
}
|
||||
|
||||
if u.PrimaryGroup != "" {
|
||||
args = append(args, "--primary-group", u.PrimaryGroup)
|
||||
args = append(args, "--gid", u.PrimaryGroup)
|
||||
}
|
||||
|
||||
if len(u.Groups) > 0 {
|
||||
|
39
test
39
test
@@ -1,10 +1,37 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Run all coreos-cloudinit tests
|
||||
# ./test
|
||||
# ./test -v
|
||||
#
|
||||
# Run tests for one package
|
||||
# PKG=initialize ./test
|
||||
#
|
||||
|
||||
echo "Building bin/coreos-cloudinit"
|
||||
. build
|
||||
# Invoke ./cover for HTML output
|
||||
COVER=${COVER:-"-cover"}
|
||||
|
||||
source ./build
|
||||
|
||||
declare -a TESTPKGS=(initialize system datasource)
|
||||
|
||||
if [ -z "$PKG" ]; then
|
||||
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
||||
# prepend repo path to each package
|
||||
TESTPKGS=${TESTPKGS[@]/#/${REPO_PATH}/}
|
||||
else
|
||||
GOFMTPATH="$TESTPKGS"
|
||||
# strip out slashes and dots from PKG=./foo/
|
||||
TESTPKGS=${PKG//\//}
|
||||
TESTPKGS=${TESTPKGS//./}
|
||||
TESTPKGS=${TESTPKGS/#/${REPO_PATH}/}
|
||||
fi
|
||||
|
||||
echo "Running tests..."
|
||||
for pkg in "./initialize ./system ./datasource"; do
|
||||
go test -i $pkg
|
||||
go test -v $pkg
|
||||
done
|
||||
go test -i ${TESTPKGS}
|
||||
go test ${COVER} $@ ${TESTPKGS}
|
||||
|
||||
echo "Checking gofmt..."
|
||||
fmtRes=$(gofmt -l $GOFMTPATH)
|
||||
|
||||
echo "Success"
|
||||
|
Reference in New Issue
Block a user