mirror of
https://github.com/glauth/glauth.git
synced 2026-10-24 05:03:11 +00:00
Modernize tests, reduce execution time from 30s to 0.2s
Make output go to test logger so that go test -v will give useful output on a failure Remove dependence on `oathtool` Remove dependence on `ls` Wait on killing the running glauth instance so subsequent tests don't fail Wait for glauth to provide authentication error instead of waiting an arbitrary amount of time
This commit is contained in:
+178
-64
@@ -1,8 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -10,6 +11,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glauth/ldap"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
type testEnv struct {
|
||||
@@ -60,12 +64,7 @@ func TestProperBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigBackendRejectsPasswordlessUserBinds(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
listener.Close()
|
||||
port := 3893
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), "passwordless.cfg")
|
||||
config := fmt.Sprintf(`debug = false
|
||||
@@ -97,11 +96,11 @@ func TestConfigBackendRejectsPasswordlessUserBinds(t *testing.T) {
|
||||
action = "search"
|
||||
object = "*"
|
||||
`, port)
|
||||
if err := os.WriteFile(configPath, []byte(config), 0600); err != nil {
|
||||
if err := os.WriteFile(configPath, []byte(config), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
svc := startSvc(SD, glauthBinary(), "-c", configPath)
|
||||
svc := startSvc(t, glauthBinary(), "-c", configPath)
|
||||
defer stopSvc(svc)
|
||||
|
||||
bindDN := "cn=nopass,dc=glauth,dc=com"
|
||||
@@ -114,7 +113,11 @@ func TestConfigBackendRejectsPasswordlessUserBinds(t *testing.T) {
|
||||
{name: "empty password", password: "", want: "exit status 53"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := doRunGetFirst(0, "ldapsearch", "-LLL", "-H", fmt.Sprintf("ldap://127.0.0.1:%d", port), "-D", bindDN, "-w", tc.password, "-x", "-b", "dc=glauth,dc=com", "(cn=nopass)")
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", fmt.Sprintf("ldap://127.0.0.1:%d", port), "-D", bindDN, "-w", tc.password, "-x", "-b", "dc=glauth,dc=com", "(cn=nopass)")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Fatalf("bind result = %q, want %q", got, tc.want)
|
||||
}
|
||||
@@ -149,7 +152,7 @@ func ldapOnlyConfig(t *testing.T, source string) string {
|
||||
|
||||
contents = contents[:start] + sectionWithoutLDAPS + contents[end:]
|
||||
path := filepath.Join(t.TempDir(), filepath.Base(source))
|
||||
if err := os.WriteFile(path, []byte(contents), 0600); err != nil {
|
||||
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
@@ -164,8 +167,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
{
|
||||
Name: "searching for the 'hackers' user",
|
||||
Check: func(t testing.TB) {
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||
if got, want := out, env.expectedaccount; got != want {
|
||||
want := env.expectedaccount
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should find them in the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -176,8 +185,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
if env.svcdnnogroup == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdnnogroup, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||
if got, want := out, env.expectedaccount; got != want {
|
||||
want := env.expectedaccount
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.svcdnnogroup, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should find them in the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -188,8 +203,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
if !env.checkbindUPN {
|
||||
t.SkipNow()
|
||||
}
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", "serviceuser@example.com", "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||
if got, want := out, env.expectedaccount; got != want {
|
||||
want := env.expectedaccount
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", "serviceuser@example.com", "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should find them in the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -197,8 +218,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
{
|
||||
Name: "querying the root SDE",
|
||||
Check: func(t testing.TB) {
|
||||
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-s", "base", "(objectclass=*)")
|
||||
if got, want := out, env.expectedinfo; got != want {
|
||||
want := env.expectedinfo
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-s", "base", "(objectclass=*)")
|
||||
got := getSecond(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should get some meta information\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -209,8 +236,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
if !env.checkanonymousrootDSE {
|
||||
t.SkipNow()
|
||||
}
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-x", "-s", "base", "(objectclass=*)")
|
||||
if got, want := out, "exit status 50"; got != want {
|
||||
want := "exit status 50"
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-x", "-s", "base", "(objectclass=*)")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should get error 50\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -218,8 +251,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
{
|
||||
Name: "enumerating posix groups",
|
||||
Check: func(t testing.TB) {
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(objectclass=posixgroup)")
|
||||
if got, want := out, env.expectedgroup; got != want {
|
||||
want := env.expectedgroup
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(objectclass=posixgroup)")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should get a list starting with the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -227,8 +266,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
{
|
||||
Name: "searching for members of the 'superheros' group",
|
||||
Check: func(t testing.TB) {
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com)")
|
||||
if got, want := out, env.expectedfirstaccount; got != want {
|
||||
want := env.expectedfirstaccount
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com)")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should get a list starting with the 'hackers' user\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -236,8 +281,13 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
{
|
||||
Name: "performing a complex search for members of 'superheros' group",
|
||||
Check: func(t testing.TB) {
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(&(objectClass=*)(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com))")
|
||||
if got, want := out, env.expectedfirstaccount; got != want {
|
||||
want := env.expectedfirstaccount
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(&(objectClass=*)(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com))")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("should get a list starting with the 'hackers' user\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -248,9 +298,21 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
if !env.checkTOTP {
|
||||
t.SkipNow()
|
||||
}
|
||||
otpvalue := doRunGetFirst(RD, "oathtool", "--totp", "-b", "-d", "6", "3hnvnk4ycv44glzigd6s25j4dougs3rk")
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret"+otpvalue, "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||
if got, want := out, env.scopedaccount; got != want {
|
||||
// We aren't checking that "github.com/pquerna/otp/totp" creates correct values.
|
||||
// That's what their own tests are for.
|
||||
// We are checking that the totp code gets through glauth code successfully
|
||||
otpvalue, err := totp.GenerateCode("3hnvnk4ycv44glzigd6s25j4dougs3rk", time.Now())
|
||||
if err != nil {
|
||||
t.Fatal("Failed to generate totp code:", err)
|
||||
}
|
||||
want := env.scopedaccount
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.otpdn, "-w", "mysecret"+otpvalue, "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||
got := getFirst(out)
|
||||
if got != want {
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
t.Log(out)
|
||||
t.Fatalf("should find them in in the 'superheros' group\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -261,8 +323,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
if !env.checkTOTP {
|
||||
t.SkipNow()
|
||||
}
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||
if got, want := out, "exit status 49"; got != want {
|
||||
want := "exit status 49"
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.otpdn, "-w", "mysecret", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should get 'Invalid credentials(49)'\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -273,8 +341,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
if !env.checkTOTP {
|
||||
t.SkipNow()
|
||||
}
|
||||
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret123456", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||
if got, want := out, "exit status 49"; got != want {
|
||||
want := "exit status 49"
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.otpdn, "-w", "mysecret123456", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
|
||||
got := getFirst(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("should get 'Invalid credentials(49)'\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -285,8 +359,14 @@ func batteryOfTests(t *testing.T, env *testEnv) {
|
||||
if env.checkemployeetype == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", env.checkemployeetype, "employeetype")
|
||||
if got, want := out, "employeetype: Intern"; got != want {
|
||||
want := "employeetype: Intern"
|
||||
out, err := doRun("ldapsearch", "-LLL", "-H", "ldap://127.0.0.1:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", env.checkemployeetype, "employeetype")
|
||||
got := getSecond(out)
|
||||
if err != nil {
|
||||
got = err.Error()
|
||||
}
|
||||
if got != want {
|
||||
t.Log(out)
|
||||
t.Fatalf("type should be 'Intern'\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
},
|
||||
@@ -316,14 +396,15 @@ func TestSampleSimple(t *testing.T) {
|
||||
checkemployeetype: "cn=hackers",
|
||||
}
|
||||
|
||||
svc := startSvc(SD, glauthBinary(), "-c", ldapOnlyConfig(t, "sample-simple.cfg"))
|
||||
svc := startSvc(t, glauthBinary(), "-c", ldapOnlyConfig(t, "sample-simple.cfg"))
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
|
||||
func TestSQLitePlugin(t *testing.T) {
|
||||
matchingLibrary := doRunGetFirst(RD, "ls", "bin/sqlite.so")
|
||||
if matchingLibrary != "bin/sqlite.so" {
|
||||
_, err := os.Stat("bin/sqlite.so")
|
||||
if err != nil {
|
||||
t.Log("Unable to find sqlite plugin")
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
@@ -342,14 +423,20 @@ func TestSQLitePlugin(t *testing.T) {
|
||||
checkemployeetype: "",
|
||||
}
|
||||
|
||||
svc := startSvc(SD, glauthBinary(), "-c", "pkg/plugins/glauth-sqlite/sample-database.cfg")
|
||||
svc := startSvc(t, glauthBinary(), "-c", "pkg/plugins/glauth-sqlite/sample-database.cfg")
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
|
||||
func TestLdapInjection(t *testing.T) {
|
||||
matchingContainers := doRunGetFirst(RD, "sh", "-c", "docker ps | grep ldap-service | wc -l")
|
||||
out, err := doRun("sh", "-c", "docker ps | grep ldap-service | wc -l")
|
||||
if err != nil {
|
||||
t.Log("Unable to find docker container:", out)
|
||||
t.SkipNow()
|
||||
}
|
||||
matchingContainers := getFirst(out)
|
||||
if matchingContainers != "1" {
|
||||
t.Log("Unable to find docker container:", out)
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
@@ -368,52 +455,79 @@ func TestLdapInjection(t *testing.T) {
|
||||
checkemployeetype: "",
|
||||
}
|
||||
|
||||
svc := startSvc(SD, glauthBinary(), "-c", "sample-ldap-injection.cfg")
|
||||
svc := startSvc(t, glauthBinary(), "-c", "sample-ldap-injection.cfg")
|
||||
batteryOfTests(t, &env)
|
||||
stopSvc(svc)
|
||||
}
|
||||
|
||||
// -----=============================================================================----
|
||||
|
||||
const SD = 3 // Start Delay
|
||||
const RD = 2 // Response Delay
|
||||
|
||||
func startSvc(delay time.Duration, name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Start()
|
||||
if delay != 0 {
|
||||
time.Sleep(time.Second * delay)
|
||||
func WaitForPort(host string, timeout time.Duration) error {
|
||||
start := time.Now()
|
||||
for {
|
||||
conn, err := ldap.Dial("tcp", host)
|
||||
if err == nil {
|
||||
err = conn.Bind("_", "_")
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("Successfully authenticated to glauth. This should not happen")
|
||||
}
|
||||
e := &ldap.Error{}
|
||||
if errors.As(err, &e) && e.ResultCode == 49 {
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
conn.Close()
|
||||
// We have an error, it's probably a connection error or an error saying that ldap isn't setup yet
|
||||
// try again
|
||||
}
|
||||
if time.Since(start) > timeout {
|
||||
return fmt.Errorf("timeout waiting for port %s: %w", host, err)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func startSvc(t *testing.T, name string, arg ...string) *exec.Cmd {
|
||||
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stderr = t.Output()
|
||||
cmd.Stdout = t.Output()
|
||||
cmd.Start()
|
||||
err := WaitForPort("127.0.0.1:3893", time.Second*2)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to wait for glauth to start:", err)
|
||||
}
|
||||
// the port is listening but the ldap server may not be accepting connections yet
|
||||
// TODO: make glauth listen as the last part of the initialization that way as soon as it listens it can process connections
|
||||
return cmd
|
||||
}
|
||||
|
||||
func stopSvc(svc *exec.Cmd) {
|
||||
svc.Process.Kill()
|
||||
svc.Process.Wait()
|
||||
}
|
||||
|
||||
func doRunGetFirst(delay time.Duration, name string, arg ...string) string {
|
||||
out := strings.SplitN(doRun(delay, name, arg...), "\n", 2)
|
||||
if out == nil || len(out) < 1 {
|
||||
func getFirst(output string) string {
|
||||
out := strings.SplitN(output, "\n", 2)
|
||||
if len(out) < 1 {
|
||||
return "*fail*"
|
||||
}
|
||||
return out[0]
|
||||
}
|
||||
|
||||
func doRunGetSecond(delay time.Duration, name string, arg ...string) string {
|
||||
out := strings.SplitN(doRun(delay, name, arg...), "\n", 3)
|
||||
if out == nil || len(out) < 2 {
|
||||
func getSecond(output string) string {
|
||||
out := strings.SplitN(output, "\n", 3)
|
||||
if len(out) < 2 {
|
||||
return "*fail*"
|
||||
}
|
||||
return out[1]
|
||||
}
|
||||
|
||||
func doRun(delay time.Duration, name string, arg ...string) string {
|
||||
out, err := exec.Command(name, arg...).Output()
|
||||
func doRun(name string, arg ...string) (string, error) {
|
||||
out, err := exec.Command(name, arg...).CombinedOutput()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
return string(bytes.TrimSpace(out)), err
|
||||
}
|
||||
if delay != 0 {
|
||||
time.Sleep(time.Second * delay)
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
|
||||
return string(bytes.TrimSpace(out)), nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/glauth/glauth/v2
|
||||
|
||||
go 1.21
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
|
||||
@@ -15,6 +15,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
|
||||
Reference in New Issue
Block a user