Compare commits

..

No commits in common. "main" and "0.0.5" have entirely different histories.
main ... 0.0.5

45 changed files with 1129 additions and 2110 deletions

View file

@ -1,19 +1,17 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.194.3/containers/go/.devcontainer/base.Dockerfile # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.194.3/containers/go/.devcontainer/base.Dockerfile
# [Choice] Go version: 1, 1.16, 1.17 # [Choice] Go version: 1, 1.16, 1.17
ARG VARIANT="1.22" ARG VARIANT="1.17"
FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT} FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT}
COPY --from=aquasec/trivy:0.38.3 /usr/local/bin/trivy /usr/bin/trivy
COPY --from=securego/gosec:2.15.0 /bin/gosec /usr/bin/gosec
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none" ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] Uncomment this section to install additional OS packages. # [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends libzmq3-dev libzmq5 # && apt-get -y install --no-install-recommends <your-package-list-here>
RUN apt-get update && apt-get install -y --no-install-recommends libzmq3-dev libzmq5
# [Optional] Uncomment the next line to use go get to install anything else you need # [Optional] Uncomment the next line to use go get to install anything else you need
# RUN go get -x <your-dependency-or-tool> # RUN go get -x <your-dependency-or-tool>

View file

@ -1,74 +1,18 @@
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.195.0/containers/go
{ {
"name": "Go", "name": "GitHub Codespaces (Default)",
"extensions": [
"GitHub.vscode-pull-request-github",
"golang.go",
"hashicorp.terraform",
"wholroyd.hcl",
"github.copilot",
"golang.Go"
],
"build": { "build": {
"dockerfile": "Dockerfile", "dockerfile": "Dockerfile",
"args": { "args": {
// Update the VARIANT arg to pick a version of Go: 1, 1.16, 1.17 "VARIANT": "1.17",
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local arm64/Apple Silicon.
"VARIANT": "1.22-bookworm",
// Options
"NODE_VERSION": "none" "NODE_VERSION": "none"
} }
}, },
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
// Set *default* container specific settings.json values on container create.
"settings": {
"go.toolsManagement.checkForUpdates": "local",
"go.useLanguageServer": true,
"go.gopath": "/go",
"go.goroot": "/usr/local/go"
},
// Use the same extensions we had on Gitpod plus HCL support.
"extensions": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"golang.Go",
"stylelint.vscode-stylelint",
"DavidAnson.vscode-markdownlint",
"Vue.volar",
"ms-azuretools.vscode-docker",
"vitest.explorer",
"GitHub.vscode-pull-request-github",
"wholroyd.hcl"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [9000],
"forwardPorts": [9101, 35570, 35571, 35572],
// Use 'portsAttributes' to set default properties for specific forwarded ports. More info: https://code.visualstudio.com/docs/remote/devcontainerjson-reference.
"portsAttributes": {
"9101": {
"label": "Prometheus",
"onAutoForward": "notify"
},
"35570": {
"label": "Router To Router",
"onAutoForward": "notify"
},
"35571": {
"label": "Worker To Router",
"onAutoForward": "notify"
},
"35572": {
"label": "Client To Router",
"onAutoForward": "notify"
}
},
// Use 'otherPortsAttributes' to configure any ports that aren't configured using 'portsAttributes'.
// "otherPortsAttributes": {
// "onAutoForward": "silent"
// },
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "go version",
// Uncomment to connect as a non-root user. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
} }

118
.drone.yml Normal file
View file

@ -0,0 +1,118 @@
---
kind: pipeline
type: docker
name: validation
platform:
arch: amd64
clone:
depth: 1
volumes:
- name: dockersock
host:
path: /run/docker.sock
steps:
- name: Notify Datadog That We Are Starting
image: masci/drone-datadog
settings:
api_key:
from_secret: Datadog
events:
- title: "Begin Build: ${DRONE_REPO}"
text: "Build ${DRONE_BUILD_NUMBER}(${DRONE_COMMIT_LINK})"
alert_type: "info"
when:
ref:
include:
- refs/tags/**
- name: Validate code base and dependencies
image: dragonheim/golang:1.17
volumes:
- name: dockersock
path: /var/run/docker.sock
environment:
TRIVY_QUIET: true
TRIVY_LIGHT: true
TRIVY_FORMAT: table
TRIVY_IGNORE_UNFIXED: true
TRIVY_NO_PROGRESS: true
commands:
# Populate temporary container with tools / files we will need for building and testing
- apk add --no-cache zeromq-dev zeromq
# Format Golang code. Golang does not really care about formatting, but this standardizes things
- go fmt ./...
# Perform basic linting of the Golang code. Ideally this should never be needed, but merges can introduce imcompatabilities.
- go vet ./...
# Perform code security check of lower level vulnerabilities. This will not break the build, we just want this information, just in case.
- trivy fs --exit-code 0 --severity UNKNOWN,LOW,MEDIUM .
# Perform code security check of higher level vulnerabilities. This can break the build.
- trivy fs --skip-update --exit-code 1 --severity CRITICAL,HIGH .
# Build new container image.
- docker buildx build --platform linux/amd64 --progress plain --build-arg SEMVER="dev" -t "${DRONE_REPO}:dev" -f assets/docker/Dockerfile .
# Perform image security check of lower level vulnerabilities. This will not break the build, we just want this information, just in case.
- trivy image --skip-update --exit-code 0 --severity UNKNOWN,LOW,MEDIUM,HIGH "${DRONE_REPO}:dev"
# Perform image security check of higher level vulnerabilities. This can break the build.
- trivy image --skip-update --exit-code 1 --severity CRITICAL "${DRONE_REPO}:dev"
# - name: Create Test Environment
# image: dragonheim/terraform:1.0
# volumes:
# - name: dockersock
# path: /var/run/docker.sock
# environment:
# TRIVY_QUIET: true
# commands:
# - cd assets/tfenv
# - terraform init
# - terraform plan
- name: Test application
image: "${DRONE_REPO}:dev"
volumes:
- name: dockersock
path: /var/run/docker.sock
commands:
- echo "running"
- name: Build and push container
image: dragonheim/golang:1.17
volumes:
- name: dockersock
path: /var/run/docker.sock
when:
ref:
include:
- refs/tags/**
commands:
# Build new container image.
- docker buildx build --platform linux/amd64 --progress plain --build-arg SEMVER="${DRONE_SEMVER}" -t "${DRONE_REPO}:latest" -f docker/Dockerfile .
# Perform image security check of higher level vulnerabilities. This can break the build.
- trivy image --skip-update --exit-code 1 --severity CRITICAL "${DRONE_REPO}:latest"
# Push new build
- docker buildx build --push --platform linux/amd64 --progress plain --build-arg SEMVER="${DRONE_SEMVER}" -t "${DRONE_REPO}:latest" -t "${DRONE_REPO}:${DRONE_SEMVER}" -f docker/Dockerfile .
- name: Notify Datadog That We Have Completed
image: masci/drone-datadog
settings:
api_key:
from_secret: Datadog
events:
- title: "Build failure on amd64"
text: "Build ${DRONE_BUILD_NUMBER}"
alert_type: "error"
when:
status:
- failure

View file

@ -1,72 +0,0 @@
name: G'Agent Scan, Build, and Test
on:
push:
branches:
- main
- releases/**
tags:
- v*
env:
SEMVAR: 0.0.13
jobs:
scan:
runs-on: docker
container:
image: dragonheim/golang:latest
steps:
- run: apk add --no-cache zeromq-dev nodejs npm
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: .cache/trivy
key: tmp.woDBBj4Baw
- run: trivy fs --no-progress --severity CRITICAL --cache-dir .cache/trivy --exit-code 1 .
- run: gosec -exclude=G114 -quiet ./...
build_simple:
needs: scan
runs-on: docker
container:
image: dragonheim/golang:latest
steps:
- run: apk add --no-cache zeromq-dev nodejs npm
- uses: actions/checkout@v4
- run: go build -o test_artifact -ldflags="-X main.Version=${{ env.SEMVAR }}" cmd/gagent/main.go
- run: ./test_artifact --version
- uses: actions/upload-artifact@v3
with:
name: test_artifact
path: test_artifact
test_simple:
needs: build_simple
runs-on: docker
container:
image: dragonheim/golang:latest
steps:
- run: apk add --no-cache zeromq-dev nodejs npm
- uses: actions/download-artifact@v3
with:
name: test_artifact
- run: chmod a+x test_artifact
- run: ./test_artifact --version
build_image:
needs: test_simple
runs-on: docker
container:
image: dragonheim/golang:latest
steps:
- run: apk add --no-cache zeromq-dev nodejs npm
- uses: actions/checkout@v4
- run: echo "${{ secrets.DOCKER_LOGIN }}" | docker login -u dragonheim2024 --password-stdin
- run: docker buildx build --push -t dragonheim/gagent:test -f assets/docker/Dockerfile --build-arg SEMVER=${{ env.SEMVAR }} .

View file

@ -0,0 +1,2 @@
# No impact in our project
CVE-2020-29652

View file

@ -1,6 +1,6 @@
# Released under MIT License # Released under MIT License
Copyright (c) 2017-2023 [James Wells](mailto:jwells@dragonheim.net?subject=G%27Agent) Copyright (c) 2017-2021 [James Wells](mailto:jwells@dragonheim.net?subject=G%27Agent)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View file

@ -1,24 +1,18 @@
# G'Agent # G'Agent
[![Maintained Status](https://img.shields.io/maintenance/yes/2024?style=plastic)](https://github.com/dragonheim/gagent) [![Maintained Status](https://img.shields.io/maintenance/yes/2021?style=plastic)](https://git.dragonheim.net/dragonheim/gagent)
[![License](https://img.shields.io/badge/License-MIT-limegreen.svg)](https://github.com/dragonheim/gagent/src/branch/main/LICENSE) [![License](https://img.shields.io/badge/License-MIT-limegreen.svg)](https://git.dragonheim.net/dragonheim/gagent/src/branch/main/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/dragonheim/gagent)](https://goreportcard.com/report/github.com/dragonheim/gagent) [![Build Status](https://drone.dragonheim.net/api/badges/dragonheim/gagent/status.svg)](https://drone.dragonheim.net/dragonheim/gagent)
[![Go Report Card](https://goreportcard.com/badge/git.dragonheim.net/dragonheim/gagent)](https://goreportcard.com/report/git.dragonheim.net/dragonheim/gagent)
[![Docker Pulls](https://img.shields.io/docker/pulls/dragonheim/gagent)](https://hub.docker.com/r/dragonheim/gagent/tags?page=1&ordering=last_updated) [![Docker Pulls](https://img.shields.io/docker/pulls/dragonheim/gagent)](https://hub.docker.com/r/dragonheim/gagent/tags?page=1&ordering=last_updated)
A Golang based mobile agent system loosely inspired by the [Agent Tcl / D'Agents](https://digitalcommons.dartmouth.edu/dissertations/62/) system created by Robert S. Gray of Dartmouth college. A Golang based mobile agent system loosely inspired by the [Agent Tcl / D'Agents](http://www.cs.dartmouth.edu/~dfk/agents/) system created by Robert S. Gray of Dartmouth college.
Please note that this program is mostly meant to be used as a tool for me to learn software development in Go, and is not meant to be used in production environments.
GPT-4 has been used in various parts of this project to debug, improve functionality and provide examples of how to get over various hurdles as I learn Go.
## Sumary
G'Agent streamlines interplanetary data searches using TCL scripts, allowing efficient communication despite time delays between Earth and Mars.
## Purpose ## Purpose
As we move closer and closer to a true space age, we need to start thinking about solutions for various space-age issues, such as the bi-directional time delay between the surface of Mars and the surface of Earth. At present, it takes between 6 and 44 minutes for a single round-trip, making most online data services unusable. G'Agent is a potential solution for data services given the time delay. As we move close and closer to a true space-age, we need to start thinking about solutions for various space-age issues such as the bi-directional time delay between the surface of Mars and the surface of Earth. At present it takes between 6 and 44 minutes for single round-trip, making most online data services unuseable. G'Agent is a potential solution for data services given the time delay.
Imagine, for a moment, that you are on Mars and need to perform a data search in a specific domain space. You would have to explain it to someone on Earth, and hope they understand enough of the domain space to know where to search and understand you well enough to perform the actual search and then send you the results. With G'Agent, instead you would write a basic script (TCL), hereafter called an agent, providing various hints about the domain space and the search as TCL code. Your client would then send it on to a server, hereafter called a router, on Earth. The router may or may not know anything about the domain space of your search, so the router will use the hints that you provide to attempt to route the agent to known workers or other routers closer to the desired domain space. Eventually your agent will reach a router whose workers can handle your search. The workers, will take the agent, run the script portion and collect the response(s), returning the reponse(s) to the router(s) for return to your client.
Imagine, for a moment, that you are on Mars and need to perform a data search in a specific domain space. You would have to explain it to someone on Earth, and hope they understand enough of the domain space to know where to search and understand you well enough to perform the actual search and then send you the results. With G'Agent, instead, you would write a basic script (TCL), hereafter called an agent, providing various hints about the domain space and the search as TCL code. Your client would then send it to a server, hereafter called a router, on Earth. The router may or may not know anything about the domain space of your search, so the router will use the hints that you provide to attempt to route the agent to known workers or other routers closer to the desired domain space. Eventually, your agent will reach a router whose workers can handle your search. The workers will take the agent, run the script portion, and collect the response(s), returning the response(s) to the router(s) for return to your client.
## Example Agent ## Example Agent
```tcl ```tcl
1 : ################### 1 : ###################
@ -40,4 +34,4 @@ Line 8 executes the hello_earth procedure defined above.
## History ## History
More information about Agent TCL / D'Agents can be found in the original [documentation](http://www.cs.dartmouth.edu/~dfk/agents/pub/agents/doc.5.1.ps.gz), and in the project's [wiki](https://github.com/dragonheim/gagent/wiki/_pages). More information about Agent TCL / D'Agents can be found in the original [documentation](http://www.cs.dartmouth.edu/~dfk/agents/pub/agents/doc.5.1.ps.gz), and in the project's [wiki](https://git.dragonheim.net/dragonheim/gagent/wiki/_pages).

View file

@ -1,5 +1,5 @@
FROM dragonheim/golang:1.23 as builder FROM dragonheim/golang:1.17 as builder
ARG SEMVER=${SEMVER:-0.0.11} ARG SEMVER
WORKDIR /gagent WORKDIR /gagent
COPY . . COPY . .
@ -9,10 +9,10 @@ ARG GOOS=${GOOS:-linux}
ARG CGO_ENABLED=1 ARG CGO_ENABLED=1
RUN apk add --no-cache zeromq-dev build-base git RUN apk add --no-cache zeromq-dev build-base git
RUN go build -o /gagent/bin/gagent -ldflags "-X main.Version=${SEMVER}" cmd/gagent/main.go RUN go build -o /gagent/bin/gagent -ldflags "-X main.semVER=${SEMVER}" cmd/gagent/main.go
RUN strip /gagent/bin/gagent RUN strip /gagent/bin/gagent
FROM alpine:3.20 FROM alpine:3.15
ARG SEMVER ARG SEMVER
LABEL Name="G'Agent" LABEL Name="G'Agent"
LABEL Maintainer="jwells@dragonheim.net" LABEL Maintainer="jwells@dragonheim.net"
@ -23,8 +23,8 @@ RUN apk add --no-cache zeromq && mkdir -p -m 0700 /etc/gagent
COPY --from=builder /gagent/assets/examples/gagent.hcl /etc/gagent/gagent.hcl COPY --from=builder /gagent/assets/examples/gagent.hcl /etc/gagent/gagent.hcl
COPY --from=builder /gagent/bin/gagent /usr/bin/ COPY --from=builder /gagent/bin/gagent /usr/bin/
# Router Client Worker Prometheus # Router Client Worker
EXPOSE 35570/tcp 35572/tcp 35571/tcp 9101/tcp EXPOSE 35570/tcp 35571/tcp 35572/tcp
VOLUME /etc/gagent VOLUME /etc/gagent
CMD ["/usr/bin/gagent"] CMD ["/usr/bin/gagent"]

View file

@ -1,13 +0,0 @@
##########################################
### Perform Fibanaci sequence up to 10 ###
##########################################
set GHINT [split "math, fib" ,]
proc fib {x} {
if {<= $x 1} {
return 1
} else {
+ [fib [- $x 1]] [fib [- $x 2]]
}
}
puts [fib 20]

View file

@ -1,18 +0,0 @@
###########################################
### Square numbers in sequence up to 10 ###
###########################################
set GHINT [split "math, square" ,]
proc square {x} {
* $x $x
}
set a 1
while {<= $a 10} {
if {== $a 5} {
puts {Missing five!}
set a [+ $a 1]
continue
}
puts "I can compute that $a*$a = [square $a]"
set a [+ $a 1]
}

View file

@ -27,7 +27,7 @@
* *
* Required. * Required.
*/ */
mode = "client" mode = "router"
/* /*
* @TODO: Add authentication based on UUID * @TODO: Add authentication based on UUID
@ -47,22 +47,13 @@ mode = "client"
*/ */
// listenaddr = 0.0.0.0 // listenaddr = 0.0.0.0
/*
* This is the port to G'Agent will listen for on
* for Prometheus queries. Can be overriden by MONITOR_PORT environment variable
* Monitoring will be disabled if this is set to 0.
*
* Optional.
*/
// monitorport = 9101
/* /*
* This is the port to the router will listen for on * This is the port to the router will listen for on
* for clients. It defaults to 35572. * for clients. It defaults to 35570.
* *
* Optional. * Optional.
*/ */
// clientport = 35572 // clientport = 35571
/* /*
* This is the port to the router will listen for on * This is the port to the router will listen for on
@ -78,7 +69,7 @@ mode = "client"
* *
* Optional. * Optional.
*/ */
// workerport = 35571 // workerport = 35572
/* /*
* @TODO * @TODO

View file

@ -1,16 +1,12 @@
{ {
"timestamp": "2021-10-25:00:00.000000000Z", "genesis_time": "2021-10-25:00:00.000000000Z",
"chainid": "gagent_ledger", "chainid": "gagent_ledger",
"agent": { "agent": {
"status": "complete",
"client": "7e9d13fe-5151-5876-66c0-20ca03e8fca4", "client": "7e9d13fe-5151-5876-66c0-20ca03e8fca4",
"shasum": "a76f7c3c7bc0f94b4f8aa63c605f8534db5675bb05d761f4461127fcadbf32d4", "shasum": "a76f7c3c7bc0f94b4f8aa63c605f8534db5675bb05d761f4461127fcadbf32d4",
"hints": {}, "status": "complete"
"script": "",
"answer": ""
}, },
"clients": { "clients": {
"clientID": "7e9d13fe-5151-5876-66c0-20ca03e8fca4" "client": "7e9d13fe-5151-5876-66c0-20ca03e8fca4"
} }
} }

View file

@ -1,9 +1,9 @@
terraform { terraform {
required_version = ">= 1.3.1" required_version = ">= 1.0.11"
required_providers { required_providers {
aws = { aws = {
source = "hashicorp/aws" source = "hashicorp/aws"
version = "~> 4.56.0" version = "~> 3.70.0"
} }
} }
} }

View file

@ -1,24 +1,22 @@
package main package main
import ( import (
fmt "fmt"
log "log" log "log"
http "net/http" http "net/http"
os "os" os "os"
strconv "strconv"
sync "sync" sync "sync"
autorestart "github.com/slayer/autorestart"
env "github.com/caarlos0/env/v6"
fqdn "github.com/Showmax/go-fqdn" fqdn "github.com/Showmax/go-fqdn"
gstructs "github.com/dragonheim/gagent/internal/gstructs" autorestart "github.com/slayer/autorestart"
gc "github.com/dragonheim/gagent/internal/client" gstructs "git.dragonheim.net/dragonheim/gagent/internal/gstructs"
gr "github.com/dragonheim/gagent/internal/router"
gs "github.com/dragonheim/gagent/internal/setup" gc "git.dragonheim.net/dragonheim/gagent/internal/client"
gw "github.com/dragonheim/gagent/internal/worker" gr "git.dragonheim.net/dragonheim/gagent/internal/router"
gs "git.dragonheim.net/dragonheim/gagent/internal/setup"
gw "git.dragonheim.net/dragonheim/gagent/internal/worker"
docopt "github.com/aviddiviner/docopt-go" docopt "github.com/aviddiviner/docopt-go"
@ -31,49 +29,31 @@ import (
uuid "github.com/jakehl/goid" uuid "github.com/jakehl/goid"
) )
var (
semVER = "0.0.4"
)
var (
wg sync.WaitGroup
)
/* /*
* Exit Codes * Exit Codes
* 0 Success * 0 Success
* 1 Environment variable parsing failed * 1 Configuration file is missing or unreadable
* 2 Configuration file is missing or unreadable * 2 Setup failed
* 3 Setup failed * 3 Invalid mode of operation
* 4 Invalid mode of operation * 4 Agent file is missing or unreadable
* 5 Agent file is missing or unreadable * 5 Agent is missing tags
* 6 Agent is missing tags * 6 No routers defined
* 7 No routers defined * 7 No workers defined
* 8 No workers defined * 8 Agent not defined
* 9 Agent not defined * 9 Agent hints / tags not defined
* 10 Agent hints / tags not defined * 10 Router not connected
* 11 Router not connected
*/ */
var environment struct {
ConfigFile string `env:"GAGENT_CONFIG" envDefault:"/etc/gagent/gagent.hcl"`
LogLevel string `env:"GAGENT_LOGLEVEL" envDefault:"WARN"`
Mode string `env:"GAGENT_MODE" envDefault:"setup"`
MonitorPort int `env:"MONITOR_PORT" envDefault:"0"`
}
/*
* This is the application configuration. It is populated from the configuration
* file and then used throughout the application.
*/
var config gstructs.GagentConfig var config gstructs.GagentConfig
/*
* We use a WaitGroup to wait for all goroutines to finish before exiting.
*/
var wg sync.WaitGroup
/*
* Set version
*/
var Version = "0.0.1"
/*
* This is the main function, and it assumes that the configuration file has
* already been read and parsed by the init() function.
*/
func main() { func main() {
log.Printf("[DEBUG] Configuration is %v\n", config) log.Printf("[DEBUG] Configuration is %v\n", config)
@ -83,7 +63,7 @@ func main() {
if len(config.Routers) == 0 { if len(config.Routers) == 0 {
log.Printf("[ERROR] No routers defined.\n") log.Printf("[ERROR] No routers defined.\n")
os.Exit(7) os.Exit(6)
} }
wg.Add(1) wg.Add(1)
@ -119,29 +99,21 @@ func main() {
default: default:
log.Printf("[ERROR] Unknown operating mode, exiting.\n") log.Printf("[ERROR] Unknown operating mode, exiting.\n")
os.Exit(4) os.Exit(3)
} }
wg.Wait() wg.Wait()
os.Exit(0) os.Exit(0)
} }
/*
* This is the init() function. It is called before the main() function, and
* it reads the configuration file, parses the command line arguments, and
* reads the environment variables. It also sets up the logging.
*/
func init() { func init() {
cfg := environment // var err error
err := env.Parse(&cfg)
if err != nil { autorestart.StartWatcher()
log.Printf("[ERROR] Failed to parse environment variables: %s\n", err)
os.Exit(1)
}
filter := &logutils.LevelFilter{ filter := &logutils.LevelFilter{
Levels: []logutils.LogLevel{"DEBUG", "INFO", "WARN", "ERROR"}, Levels: []logutils.LogLevel{"DEBUG", "INFO", "WARN", "ERROR"},
MinLevel: logutils.LogLevel(cfg.LogLevel), MinLevel: logutils.LogLevel("DEBUG"),
Writer: os.Stderr, Writer: os.Stderr,
} }
log.SetOutput(filter) log.SetOutput(filter)
@ -151,9 +123,9 @@ func init() {
/* /*
* Initialize the configuration * Initialize the configuration
*/ */
config.Version = Version config.Version = semVER
config.File = cfg.ConfigFile config.File = "/etc/gagent/gagent.hcl"
config.Mode = "setup" config.Mode = "setup"
@ -167,30 +139,37 @@ func init() {
config.UUID = uuid.NewV4UUID().String() config.UUID = uuid.NewV4UUID().String()
/* /*
* By default, we want to listen on all IP addresses. * By default, we want to listen on all IP addresses. It can be overridden
* in the configuration file by setting listenaddr
*/ */
config.ListenAddr = "0.0.0.0" config.ListenAddr = "0.0.0.0"
/* /*
* G'Agent will use this port for monitoring via prometheus., If set * By default, G'Agent will use port 9101 or monitoring via prometheus.
* is set to 0, G'Agent will not listen for prometheus metrics. * It can be overridden in the configuration file by setting clientport
*/ */
config.MonitorPort = cfg.MonitorPort config.MonitorPort = 9101
/* /*
* G'Agent client will use this port to communicate with the routers. * By default, G'Agent client will use port 35571 to communicate with the
* routers, but you can override it by setting the clientport in the
* configuration file
*/ */
config.ClientPort = 35572 config.ClientPort = 35571
/* /*
* G'Agent router will use this port to communicate with other routers. * By default, G'Agent router will use port 35572 to communicate with
* other routers, but you can override it by setting the routerport in
* the configuration file
*/ */
config.RouterPort = 35570 config.RouterPort = 35570
/* /*
* G'Agent worker will use this port to communicate with the routers. * By default, G'Agent worker will use port 35570 to communicate with the
* routers, but you can override it by setting the workerport in the
* configuration file
*/ */
config.WorkerPort = 35571 config.WorkerPort = 35572
config.Clients = make([]*gstructs.ClientDetails, 0) config.Clients = make([]*gstructs.ClientDetails, 0)
config.Routers = make([]*gstructs.RouterDetails, 0) config.Routers = make([]*gstructs.RouterDetails, 0)
@ -225,41 +204,27 @@ func init() {
usage += "\n" usage += "\n"
usage += "Options:\n" usage += "Options:\n"
usage += " -h, --help -- Show this help screen and exit\n" usage += " -h --help -- Show this help screen and exit \n"
usage += " -v, --version -- Show version and exit\n" usage += " --version -- Show version and exit \n"
usage += " --config=<config> -- [default: /etc/gagent/gagent.hcl] \n" usage += " --config=<config> -- [default: /etc/gagent/gagent.hcl] \n"
usage += " --agent=<file> -- filename of the agent to be uploaded to the G'Agent network. Required in push mode \n" usage += " --agent=<file> -- filename of the agent to be uploaded to the G'Agent network. Required in push mode \n"
usage += "\n" usage += "\n"
usage += "Environment Variables:\n"
usage += " GAGENT_CONFIG -- [default: /etc/gagent/gagent.hcl]\n"
usage += " GAGENT_MONITOR -- [default: 0]\n"
usage += " MODE -- [default: setup]\n"
usage += "\n"
usage += "Examples:\n"
usage += " gagent client pull --config=/etc/gagent/gagent.hcl\n"
usage += " gagent client push --config=/etc/gagent/gagent.hcl --agent=/tmp/agent.tcl\n"
usage += " gagent router --config=/etc/gagent/gagent.hcl\n"
usage += " gagent worker --config=/etc/gagent/gagent.hcl\n"
usage += " gagent setup --config=/etc/gagent/gagent.hcl\n"
usage += "\n"
/* /*
* Consume the usage variable and the command line arguments to create a * Consume the usage variable and the command line arguments to create a
* dictionary / map. * dictionary / map.
*/ */
opts, _ := docopt.ParseArgs(usage, nil, config.Version) opts, _ := docopt.ParseArgs(usage, nil, semVER)
log.Printf("[DEBUG] Arguments are %v\n", opts) log.Printf("[DEBUG] Arguments are %v\n", opts)
if opts["--config"] != nil { if opts["--config"] != nil {
config.File = opts["--config"].(string) config.File = opts["--config"].(string)
} }
err = hclsimple.DecodeFile(config.File, nil, &config) err := hclsimple.DecodeFile(config.File, nil, &config)
if err != nil && opts["setup"] == false { if err != nil && opts["setup"] == false {
log.Printf("[ERROR] Failed to load configuration file: %s.\n", config.File) log.Printf("[ERROR] Failed to load configuration file: %s.\n", config.File)
os.Exit(2) os.Exit(1)
} }
/* /*
@ -274,7 +239,7 @@ func init() {
log.Printf("[ERROR] Agent file not specified") log.Printf("[ERROR] Agent file not specified")
os.Exit(8) os.Exit(8)
} else { } else {
config.Agent = opts["--agent"].(string) config.File = opts["--agent"].(string)
} }
if opts["pull"] == true { if opts["pull"] == true {
@ -295,16 +260,11 @@ func init() {
} }
} }
log.Printf("[DEBUG] Config is %v\n", config)
/* /*
* Start Prometheus metrics exporter * Start Prometheus metrics exporter
*/ */
if config.MonitorPort != 0 { go http.ListenAndServe(fmt.Sprintf("%s:%d", config.ListenAddr, config.MonitorPort), nil)
go func() {
log.Printf("[INFO] Starting Prometheus metrics exporter on port %d\n", config.MonitorPort)
err := http.ListenAndServe(string(config.ListenAddr)+":"+strconv.Itoa(config.MonitorPort), nil)
log.Printf("[ERROR] Prometheus metrics exporter returned: %s\n", err)
}()
}
autorestart.WatchFilename = config.File
autorestart.StartWatcher()
} }

View file

@ -1,120 +0,0 @@
package main
import (
"bytes"
"log"
"net/http"
"net/http/httptest"
"os"
"testing"
env "github.com/caarlos0/env/v6"
gstructs "github.com/dragonheim/gagent/internal/gstructs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// Mocking the WaitGroup to avoid actually waiting in tests
type MockWaitGroup struct {
mock.Mock
}
func (m *MockWaitGroup) Add(delta int) {
m.Called(delta)
}
func (m *MockWaitGroup) Done() {
m.Called()
}
func (m *MockWaitGroup) Wait() {
m.Called()
}
// Mocking the config loader function to inject test configurations
func mockInitConfig() {
config = gstructs.GagentConfig{
Mode: "client",
MonitorPort: 8080,
// Populate other required fields as needed
}
}
func TestMainFunction(t *testing.T) {
var wg MockWaitGroup
wg.On("Add", 1).Return()
wg.On("Wait").Return()
wg.On("Done").Return()
mockInitConfig()
// Test the client mode
config.Mode = "client"
main()
wg.AssertCalled(t, "Add", 1)
// Test the router mode
config.Mode = "router"
main()
wg.AssertCalled(t, "Add", 1)
// Test the worker mode
config.Mode = "worker"
main()
wg.AssertCalled(t, "Add", 1)
// Test the setup mode
config.Mode = "setup"
main()
wg.AssertCalled(t, "Add", 1)
// Test an invalid mode
config.Mode = "invalid"
assert.Panics(t, func() { main() }, "Expected main() to panic with invalid mode")
}
func TestInitFunction(t *testing.T) {
// Backup original stdout and defer restoration
origStdout := os.Stdout
defer func() { os.Stdout = origStdout }()
// Capture stdout output to test log output
var logOutput bytes.Buffer
log.SetOutput(&logOutput)
// Test init
// init() #@TODO Write a useful test for init
// Assertions
assert.Contains(t, logOutput.String(), "[DEBUG] Arguments are")
assert.NotEmpty(t, config.Version, "Config version should not be empty")
assert.NotEmpty(t, config.UUID, "Config UUID should not be empty")
}
func TestPrometheusMetricsExporter(t *testing.T) {
mockInitConfig()
config.MonitorPort = 8080
req, err := http.NewRequest("GET", "/metrics", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.DefaultServeMux.ServeHTTP(w, r)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, "Handler returned wrong status code")
assert.Contains(t, rr.Body.String(), "go_gc_duration_seconds", "Expected metrics output")
}
func TestEnvironmentParsing(t *testing.T) {
cfg := environment
err := env.Parse(&cfg)
assert.NoError(t, err)
assert.Equal(t, "/etc/gagent/gagent.hcl", cfg.ConfigFile, "Expected default config file path")
assert.Equal(t, "WARN", cfg.LogLevel, "Expected default log level")
assert.Equal(t, "setup", cfg.Mode, "Expected default mode")
assert.Equal(t, 0, cfg.MonitorPort, "Expected default monitor port")
}

View file

@ -1,4 +1,4 @@
### [0.0.0](https://github.com/dragonheim/gagent/commit/5863999) (20210425) ### [0.0.0](https://git.dragonheim.net/dragonheim/gagent/commit/5863999) (20210425)
--- ---
#### Features #### Features
* [Initial Commit](https://github.com/dragonheim/gagent/commit/5863999) -- James Wells * [Initial Commit](https://git.dragonheim.net/dragonheim/gagent/commit/5863999) -- James Wells

View file

@ -6,7 +6,7 @@ Document the G'Agent TCL language extension.
We are using [Lain Dono](mailto:lain.dono@gmail.com)'s [Picol](https://github.com/lain-dono/picol.go) package though we will be modifying it specific to support a subset of the [Agent Tcl / D'Agents](http://www.cs.dartmouth.edu/~dfk/agents/pub/agents/doc.5.1.ps.gz) language extensions and modern versions of Go. We are using [Lain Dono](mailto:lain.dono@gmail.com)'s [Picol](https://github.com/lain-dono/picol.go) package though we will be modifying it specific to support a subset of the [Agent Tcl / D'Agents](http://www.cs.dartmouth.edu/~dfk/agents/pub/agents/doc.5.1.ps.gz) language extensions and modern versions of Go.
The language extensions can be found in the [LANGUAGE.md](https://github.com/dragonheim/gagent/src/branch/main/LANGUAGE.md) file. The language extensions can be found in the [LANGUAGE.md](https://git.dragonheim.net/dragonheim/gagent/src/branch/main/LANGUAGE.md) file.
## AgentTCL Language Extension ## AgentTCL Language Extension

View file

@ -1,32 +1,34 @@
### [Source Code Scan](#source) ### [Source Code Scan](#source)
```
2023-03-15T06:59:15.989-0700 INFO Need to update DB
2023-03-15T06:59:15.989-0700 INFO DB Repository: ghcr.io/aquasecurity/trivy-db
2023-03-15T06:59:15.989-0700 INFO Downloading DB...
36.01 MiB / 36.01 MiB [-----------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 7.93 MiB p/s 4.7s
2023-03-15T06:59:21.791-0700 INFO Vulnerability scanning is enabled
2023-03-15T06:59:21.791-0700 INFO Secret scanning is enabled
2023-03-15T06:59:21.791-0700 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning
2023-03-15T06:59:21.791-0700 INFO Please see also https://aquasecurity.github.io/trivy/v0.37/docs/secret/scanning/#recommendation for faster secret detection
2023-03-15T06:59:25.698-0700 INFO Number of language-specific files: 1
2023-03-15T06:59:25.699-0700 INFO Detecting gomod vulnerabilities...
IGNORED: We are not using the SSH features of golang.org/x/crypto
```
2021-11-13T10:25:13.188-0800 INFO Need to update DB
2021-11-13T10:25:13.188-0800 INFO Downloading DB...
24.70 MiB / 24.70 MiB [----------------------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 6.04 MiB p/s 4s
2021-11-13T10:25:18.570-0800 INFO Detected OS: unknown
2021-11-13T10:25:18.570-0800 INFO Number of PL dependency files: 2
2021-11-13T10:25:18.570-0800 INFO Detecting gobinary vulnerabilities...
2021-11-13T10:25:18.571-0800 INFO Detecting gomod vulnerabilities...
bin/gagent
==========
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
```
go.sum
======
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0)
+---------------------+------------------+----------+-----------------------------------+------------------------------------+---------------------------------------+
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
+---------------------+------------------+----------+-----------------------------------+------------------------------------+---------------------------------------+
| golang.org/x/crypto | CVE-2020-29652 | HIGH | 0.0.0-20200622213623-75b288015ac9 | v0.0.0-20201216223049-8b5274cf687f | golang: crypto/ssh: crafted |
| | | | | | authentication request can |
| | | | | | lead to nil pointer dereference |
| | | | | | -->avd.aquasec.com/nvd/cve-2020-29652 |
+---------------------+------------------+----------+-----------------------------------+------------------------------------+---------------------------------------+
```
--- ---
### [Image Scan](#image) ### [Image Scan](#image)
```
2023-03-15T07:05:44.377-0700 INFO Vulnerability scanning is enabled
2023-03-15T07:05:44.377-0700 INFO Secret scanning is enabled
2023-03-15T07:05:44.377-0700 INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning
2023-03-15T07:05:44.377-0700 INFO Please see also https://aquasecurity.github.io/trivy/v0.37/docs/secret/scanning/#recommendation for faster secret detection
2023-03-15T07:05:44.731-0700 INFO Detected OS: alpine
2023-03-15T07:05:44.731-0700 INFO Detecting Alpine vulnerabilities...
2023-03-15T07:05:44.732-0700 INFO Number of language-specific files: 1
2023-03-15T07:05:44.732-0700 INFO Detecting gobinary vulnerabilities...
dragonheim/gagent:0.0.7 (alpine 3.17.2) NONE
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
```

47
go.mod
View file

@ -1,43 +1,32 @@
module github.com/dragonheim/gagent module git.dragonheim.net/dragonheim/gagent
go 1.23 go 1.17
require ( require (
github.com/Showmax/go-fqdn v1.0.0 github.com/Showmax/go-fqdn v1.0.0
github.com/aviddiviner/docopt-go v0.0.0-20170807220726-d8a1d67efc6a github.com/aviddiviner/docopt-go v0.0.0-20170807220726-d8a1d67efc6a
github.com/caarlos0/env/v6 v6.10.1 github.com/hashicorp/hcl/v2 v2.11.1
github.com/carlmjohnson/versioninfo v0.22.5
github.com/hashicorp/hcl/v2 v2.22.0
github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/logutils v1.0.0
github.com/jakehl/goid v1.1.0 github.com/jakehl/goid v1.1.0
github.com/pebbe/zmq4 v1.2.11 github.com/pebbe/zmq4 v1.2.7
github.com/prometheus/client_golang v1.20.3 github.com/prometheus/client_golang v1.11.0
github.com/slayer/autorestart v0.0.0-20170706172547-5ebd91f955ae github.com/slayer/autorestart v0.0.0-20170706172704-7bc8d250279b
github.com/stretchr/testify v1.9.0 github.com/zclconf/go-cty v1.10.0
github.com/zclconf/go-cty v1.15.0
) )
require ( require (
github.com/agext/levenshtein v1.2.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.5.6 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/common v0.59.1 // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
github.com/prometheus/procfs v0.15.1 // indirect golang.org/x/text v0.3.7 // indirect
github.com/stretchr/objx v0.5.2 // indirect google.golang.org/protobuf v1.27.1 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

557
go.sum
View file

@ -1,82 +1,517 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM= github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM=
github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko= github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/aviddiviner/docopt-go v0.0.0-20170807220726-d8a1d67efc6a h1:YJuVATwP+Gzk7nys0U/DKjKkoYp1n/sYm0yi5vX8W8M= github.com/aviddiviner/docopt-go v0.0.0-20170807220726-d8a1d67efc6a h1:YJuVATwP+Gzk7nys0U/DKjKkoYp1n/sYm0yi5vX8W8M=
github.com/aviddiviner/docopt-go v0.0.0-20170807220726-d8a1d67efc6a/go.mod h1:pBRbUcGboHT5qBceq2Cg/WIcDbO78a8wPxTg8zmS3Hs= github.com/aviddiviner/docopt-go v0.0.0-20170807220726-d8a1d67efc6a/go.mod h1:pBRbUcGboHT5qBceq2Cg/WIcDbO78a8wPxTg8zmS3Hs=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc=
github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jakehl/goid v1.1.0 h1:c08GO8z16wWJtfQhyiD8BQRFkpf1oDxaPmA+uYAG+50= github.com/jakehl/goid v1.1.0 h1:c08GO8z16wWJtfQhyiD8BQRFkpf1oDxaPmA+uYAG+50=
github.com/jakehl/goid v1.1.0/go.mod h1:V6bQh+tr2Oay5WHL0jmTTJWrABYIO+cs4/P6e1prV1o= github.com/jakehl/goid v1.1.0/go.mod h1:V6bQh+tr2Oay5WHL0jmTTJWrABYIO+cs4/P6e1prV1o=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/pebbe/zmq4 v1.2.11 h1:Ua5mgIaZeabUGnH7tqswkUcjkL7JYGai5e8v4hpEU9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pebbe/zmq4 v1.2.11/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pebbe/zmq4 v1.2.7 h1:6EaX83hdFSRUEhgzSW1E/SPoTS3JeYZgYkBvwdcrA9A=
github.com/pebbe/zmq4 v1.2.7/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/slayer/autorestart v0.0.0-20170706172547-5ebd91f955ae h1:hnJJroq/kooxO2jUKDc8KXxj8tilWvOlD0hzDDv05ss= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/slayer/autorestart v0.0.0-20170706172547-5ebd91f955ae/go.mod h1:p+QQKBy7tS+myk+y3sgnAKx4gUtD/Q9Z6KEd77cLzWY= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= github.com/slayer/autorestart v0.0.0-20170706172704-7bc8d250279b h1:3EujQY7LEbzy5paxa0S2OrsL6+vTwYiUU/R272YlwiQ=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= github.com/slayer/autorestart v0.0.0-20170706172704-7bc8d250279b/go.mod h1:p+QQKBy7tS+myk+y3sgnAKx4gUtD/Q9Z6KEd77cLzWY=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0=
github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -1,102 +1,67 @@
package chaindb package chaindb
import ( import (
sha256 "crypto/sha256" sha "crypto/sha256"
fmt "fmt" fmt "fmt"
log "log" log "log"
os "os"
time "time" time "time"
gs "github.com/dragonheim/gagent/internal/gstructs" gstructs "git.dragonheim.net/dragonheim/gagent/internal/gstructs"
cty "github.com/zclconf/go-cty/cty"
hclsimple "github.com/hashicorp/hcl/v2/hclsimple" hclsimple "github.com/hashicorp/hcl/v2/hclsimple"
hclwrite "github.com/hashicorp/hcl/v2/hclwrite" // hclwrite "github.com/hashicorp/hcl/v2/hclwrite"
) )
type GagentDb struct { type GagentDb struct {
ChainRow []*GagentDbRow `hcl:"timestamp,block"` chainRow []*gagentDbRow `hcl:"timestamp,block"`
} }
type GagentDbRow struct { type gagentDbRow struct {
Timestamp time.Time `hcl:"timestamp"` timestamp time.Time `hcl:"timestamp"`
DBName string `hcl:"chainid,optional"` DBName string `hcl:"chainid,optional"`
Agent gs.AgentDetails `hcl:"agent,block"` Agent gstructs.AgentDetails `hcl:"agent,block"`
DbCurrHash [32]byte `hcl:"currhash"` dbCurrHash [32]byte `hcl:"currhash"`
DbPrevHash [32]byte `hcl:"prevhash"` dbPrevHash [32]byte `hcl:"prevhash"`
} }
/* /*
* Initialize the database * Initialize the database
*/ */
func NewGagentDb() *GagentDb { func (db *GagentDb) Init() {
return &GagentDb{ db.chainRow = make([]*gagentDbRow, 0)
ChainRow: make([]*GagentDbRow, 0),
}
} }
/* /*
* Load the database from disk * Load the database from disk
*/ */
func (db *GagentDb) LoadHCL(ChainDBPath string) error { func (db *GagentDb) Load() error {
err := hclsimple.DecodeFile(ChainDBPath, nil, db) err := hclsimple.DecodeFile("chaindb.hcl", nil, &db)
if err != nil {
return err
}
log.Printf("[DEBUG] DB values: %v\n", db) log.Printf("[DEBUG] DB values: %v\n", db)
return nil return err
}
/*
* Write the database to an HCL file
*/
func (db *GagentDb) WriteHCL(ChainDBPath string) error {
f := hclwrite.NewEmptyFile()
rootBody := f.Body()
for _, row := range db.ChainRow {
rowBlock := rootBody.AppendNewBlock("row", []string{})
rowBody := rowBlock.Body()
rowBody.SetAttributeValue("timestamp", cty.StringVal(row.Timestamp.Format(time.RFC3339)))
rowBody.SetAttributeValue("chainid", cty.StringVal(row.DBName))
rowBody.SetAttributeValue("currhash", cty.StringVal(fmt.Sprintf("%x", row.DbCurrHash)))
rowBody.SetAttributeValue("prevhash", cty.StringVal(fmt.Sprintf("%x", row.DbPrevHash)))
agentBlock := rowBody.AppendNewBlock("agent", []string{})
agentBody := agentBlock.Body()
agentBody.SetAttributeValue("name", cty.StringVal(row.Agent.Client))
agentBody.SetAttributeValue("version", cty.StringVal(row.Agent.Shasum))
}
return os.WriteFile(ChainDBPath, f.Bytes(), 0600)
} }
/* /*
* Add a new row to the chaindb * Add a new row to the chaindb
*/ */
func (db *GagentDb) AddRow(row *GagentDbRow) { func (db *GagentDb) AddRow(row *gagentDbRow) error {
row.Timestamp = time.Now() row.timestamp = time.Now()
db.ChainRow = append(db.ChainRow, row) db.chainRow = append(db.chainRow, row)
db.SetCurrHash()
db.SetPrevHash() return nil
} }
/* /*
* Set current hash of the database * Set current hash of the database
*/ */
func (db *GagentDb) SetCurrHash() { func (db *GagentDb) SetCurrHash() {
row := db.ChainRow[len(db.ChainRow)-1] db.chainRow[len(db.chainRow)-1].dbCurrHash = [32]byte{}
row.DbCurrHash = sha256.Sum256([]byte(fmt.Sprintf("%v", db))) foo := sha.Sum256([]byte(fmt.Sprintf("%v", db)))
db.chainRow[len(db.chainRow)-1].dbCurrHash = foo
} }
/* /*
* Set previous hash of the database * Set previous hash of the database
*/ */
func (db *GagentDb) SetPrevHash() { func (db *GagentDb) SetPrevHash() {
row := db.ChainRow[len(db.ChainRow)-1] db.chainRow[len(db.chainRow)-1].dbPrevHash = db.chainRow[len(db.chainRow)-1].dbCurrHash
if len(db.ChainRow) > 1 {
row.DbPrevHash = db.ChainRow[len(db.ChainRow)-2].DbCurrHash
}
} }

View file

@ -1,65 +0,0 @@
package chaindb
import (
bytes "bytes"
os "os"
testing "testing"
time "time"
gs "github.com/dragonheim/gagent/internal/gstructs"
)
const testChainDBPath = "/tmp/test_chaindb.hcl"
func TestGagentDb(t *testing.T) {
// Create a new GagentDb
db := NewGagentDb()
// Add a row to the database
row := &GagentDbRow{
DBName: "testDB",
Agent: gs.AgentDetails{
Client: "testAgent",
Shasum: "v1.0.0",
},
}
db.AddRow(row)
// Check if the row was added correctly
if len(db.ChainRow) != 1 {
t.Errorf("Expected length of ChainRow to be 1, but got %d", len(db.ChainRow))
}
// Check if the timestamp was set correctly
if db.ChainRow[0].Timestamp.After(time.Now()) {
t.Error("Timestamp is incorrectly set in the future")
}
// Write the database to an HCL file
err := db.WriteHCL(testChainDBPath)
if err != nil {
t.Errorf("Error writing HCL file: %v", err)
}
// Load the database from the HCL file
loadedDb := NewGagentDb()
err = loadedDb.LoadHCL(testChainDBPath)
if err != nil {
t.Errorf("Error loading HCL file: %v", err)
}
// Check if the loaded database is the same as the original one
if !bytes.Equal(loadedDb.ChainRow[0].DbCurrHash[:], db.ChainRow[0].DbCurrHash[:]) {
t.Error("Loaded database has a different current hash than the original one")
}
if !bytes.Equal(loadedDb.ChainRow[0].DbPrevHash[:], db.ChainRow[0].DbPrevHash[:]) {
t.Error("Loaded database has a different previous hash than the original one")
}
// Clean up the test HCL file
err = os.Remove(testChainDBPath)
if err != nil {
t.Errorf("Error cleaning up test HCL file: %v", err)
}
}

View file

@ -2,49 +2,47 @@ package client
import ( import (
sha "crypto/sha256" sha "crypto/sha256"
hex "encoding/hex"
fmt "fmt" fmt "fmt"
ioutil "io/ioutil"
log "log" log "log"
os "os" os "os"
regexp "regexp" regexp "regexp"
strconv "strconv"
strings "strings" strings "strings"
sync "sync" sync "sync"
time "time" time "time"
gs "github.com/dragonheim/gagent/internal/gstructs" gstructs "git.dragonheim.net/dragonheim/gagent/internal/gstructs"
zmq "github.com/pebbe/zmq4" zmq "github.com/pebbe/zmq4"
) )
/* /*
* Client mode will send an agent file to a router for processing Client mode will send an agent file to a router for processing
* Clients do not process the agent files, only send them as Clients do not process the agent files, only send them as
* requests to a router. If started without arguments, the client requests to a router. If started without arguments, the client
* will contact the router and attempt to retrieve the results will contact the router and attempt to retrieve the results
* of it's most recent request. of it's most recent request.
* Main is the entrypoint for the client process Main is the entrypoint for the client process
*/ */
func Main(wg *sync.WaitGroup, config gs.GagentConfig) { func Main(wg *sync.WaitGroup, config gstructs.GagentConfig) {
log.Printf("[INFO] Starting client\n") log.Printf("[INFO] Starting client\n")
defer wg.Done() defer wg.Done()
var agent gs.AgentDetails var agent gstructs.AgentDetails
var err error var err error
if config.CMode { if config.CMode {
agent.Script, err = os.ReadFile(config.Agent) agent.ScriptCode, err = ioutil.ReadFile(config.File)
if err != nil { if err != nil {
log.Printf("[ERROR] No such file or directory: %s", config.Agent) log.Printf("[ERROR] No such file or directory: %s", config.File)
os.Exit(4) os.Exit(4)
} }
log.Printf("[DEBUG] Agent file contents: \n----- -----\n%s\n----- -----\n", agent.Script) log.Printf("[DEBUG] Agent file contents: \n----- -----\n%s\n----- -----\n", agent.ScriptCode)
} }
agent.Client = config.UUID agent.Client = config.UUID
tmpsum := sha.Sum256([]byte(agent.Script)) agent.Shasum = fmt.Sprintf("%s", sha.Sum256(agent.ScriptCode))
agent.Shasum = fmt.Sprintf("%v", hex.EncodeToString(tmpsum[:]))
log.Printf("[INFO] SHA256 of Agent file: %s", agent.Shasum) log.Printf("[INFO] SHA256 of Agent file: %s", agent.Shasum)
agent.Status = 1 agent.Status = "loaded"
agent.Hints = getTagsFromHints(agent) agent.Hints = getTagsFromHints(agent)
agent.Answer = nil agent.Answer = nil
@ -56,7 +54,7 @@ func Main(wg *sync.WaitGroup, config gs.GagentConfig) {
if config.Routers[key].ClientPort != 0 { if config.Routers[key].ClientPort != 0 {
rport = config.Routers[key].ClientPort rport = config.Routers[key].ClientPort
} }
connectString := "tcp://" + config.Routers[key].RouterAddr + ":" + strconv.Itoa(rport) connectString := fmt.Sprintf("tcp://%s:%d", config.Routers[key].RouterAddr, rport)
wg.Add(1) wg.Add(1)
go sendAgent(wg, config.UUID, connectString, agent) go sendAgent(wg, config.UUID, connectString, agent)
@ -66,29 +64,21 @@ func Main(wg *sync.WaitGroup, config gs.GagentConfig) {
/* /*
* Parse Agent file for GHINT data to populate the G'Agent hints * Parse Agent file for GHINT data to populate the G'Agent hints
*/ */
func getTagsFromHints(agent gs.AgentDetails) []string { func getTagsFromHints(agent gstructs.AgentDetails) []string {
var tags []string var tags []string
re := regexp.MustCompile(`\s*set\s+GHINT\s*\[\s*split\s*"(?P<Hints>.+)"\s*\,\s*\]`)
// Use named capture groups to extract the hints res := re.FindStringSubmatch(string(agent.ScriptCode))
re := regexp.MustCompile(`^*set\s+GHINT\s*\[\s*split\s*"(?P<Hints>[^"]+)"\s*,\s*\]`) if len(res) < 1 {
res := re.FindStringSubmatch(string(agent.Script))
// If we don't have at least 2 matches, we have no hints
if len(res) < 2 {
log.Printf("[ERROR] Agent is missing GHINT tags") log.Printf("[ERROR] Agent is missing GHINT tags")
os.Exit(4) os.Exit(4)
} }
tags = strings.Split(res[1], ",")
// Use named capturing group index
hintsIndex := re.SubexpIndex("Hints")
tags = strings.Split(res[hintsIndex], ",")
log.Printf("[DEBUG] G'Agent hints: %v\n", tags) log.Printf("[DEBUG] G'Agent hints: %v\n", tags)
return tags return tags
} }
func sendAgent(wg *sync.WaitGroup, uuid string, connectString string, agent gs.AgentDetails) { func sendAgent(wg *sync.WaitGroup, uuid string, connectString string, agent gstructs.AgentDetails) {
defer wg.Done() defer wg.Done()
var mu sync.Mutex var mu sync.Mutex
@ -107,10 +97,10 @@ func sendAgent(wg *sync.WaitGroup, uuid string, connectString string, agent gs.A
} }
log.Printf("[DEBUG] Start sending agent...\n") log.Printf("[DEBUG] Start sending agent...\n")
agent.Status = 2
status, err := sock.SendMessage(agent) status, err := sock.SendMessage(agent)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed to send agent to router\n") log.Printf("[ERROR] Failed to send agent to router\n")
// os.Exit(11)
return return
} }
log.Printf("[DEBUG] Agent send status: %d\n", status) log.Printf("[DEBUG] Agent send status: %d\n", status)

View file

@ -1,153 +0,0 @@
package client
import (
bytes "bytes"
errors "errors"
io "io"
log "log"
os "os"
sync "sync"
testing "testing"
gs "github.com/dragonheim/gagent/internal/gstructs"
zmq "github.com/pebbe/zmq4"
)
type mockSocket struct {
sendMessageError error
}
func (m *mockSocket) Close() error { return nil }
func (m *mockSocket) Bind(endpoint string) error { return nil }
func (m *mockSocket) Connect(endpoint string) error { return nil }
func (m *mockSocket) SetIdentity(identity string) error { return nil }
func (m *mockSocket) SendMessage(parts ...interface{}) (int, error) { return 0, m.sendMessageError }
func (m *mockSocket) RecvMessage(flags zmq.Flag) ([]string, error) { return nil, nil }
func (m *mockSocket) RecvMessageBytes(flags zmq.Flag) ([][]byte, error) { return nil, nil }
func (m *mockSocket) RecvMessageString(flags zmq.Flag) ([]string, error) { return nil, nil }
func (m *mockSocket) SetSubscribe(filter string) error { return nil }
func (m *mockSocket) SetUnsubscribe(filter string) error { return nil }
func (m *mockSocket) Send(msg string, flags zmq.Flag) (int, error) { return 0, nil }
func (m *mockSocket) SendBytes(msg []byte, flags zmq.Flag) (int, error) { return 0, nil }
func (m *mockSocket) SendFrame(msg []byte, flags zmq.Flag) (int, error) { return 0, nil }
func (m *mockSocket) SendMultipart(parts [][]byte, flags zmq.Flag) (int, error) { return 0, nil }
func (m *mockSocket) Recv(flags zmq.Flag) (string, error) { return "", nil }
func (m *mockSocket) RecvBytes(flags zmq.Flag) ([]byte, error) { return nil, nil }
func (m *mockSocket) RecvFrame(flags zmq.Flag) ([]byte, error) { return nil, nil }
func (m *mockSocket) RecvMultipart(flags zmq.Flag) ([][]byte, error) { return nil, nil }
func (m *mockSocket) SetOption(option zmq.SocketOption, value interface{}) error { return nil }
func (m *mockSocket) GetOption(option zmq.SocketOption) (interface{}, error) { return nil, nil }
func (m *mockSocket) Events() zmq.State { return 0 }
func (m *mockSocket) String() string { return "" }
func TestGetTagsFromHints(t *testing.T) {
agent := gs.AgentDetails{
Script: []byte(`*set GHINT[split "tag1,tag2,tag3",]`),
}
expectedHints := []string{"tag1", "tag2", "tag3"}
hints := getTagsFromHints(agent)
if !equalStringSlices(hints, expectedHints) {
t.Errorf("Expected hints %v, but got %v", expectedHints, hints)
}
}
func TestSendAgent(t *testing.T) {
wg := &sync.WaitGroup{}
config := gs.GagentConfig{
UUID: "test-uuid",
ClientPort: 1234,
Routers: map[string]gs.Router{
"test-router": {
RouterAddr: "127.0.0.1",
ClientPort: 1234,
},
},
}
agent := gs.AgentDetails{
Client: "test-client",
Script: []byte(`*set GHINT[split "tag1,tag2,tag3",]`),
}
// Replace zmq.NewSocket with a function that returns a mock socket
origNewSocket := newSocket
defer func() { newSocket = origNewSocket }()
newSocket = func(t zmq.Type) (zmq.Socket, error) {
return &mockSocket{}, nil
}
wg.Add(1)
go sendAgent(wg, config.UUID, "tcp://127.0.0.1:1234", agent)
wg.Wait()
// Test with an error in sending a message
newSocket = func(t zmq.Type) (zmq.Socket, error) {
return &mockSocket{sendMessageError: errors.New("send message error")}, nil
}
wg.Add(1)
go sendAgent(wg, config.UUID, "tcp://127.0.0.1:1234", agent)
wg.Wait()
}
func equalStringSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func TestMain(t *testing.T) {
// Prepare a temporary agent file for testing
tmpAgentFile, err := io.TempFile("", "agent")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpAgentFile.Name())
content := []byte(`*set GHINT[split "tag1,tag2,tag3",]`)
if _, err := tmpAgentFile.Write(content); err != nil {
t.Fatal(err)
}
if err := tmpAgentFile.Close(); err != nil {
t.Fatal(err)
}
config := gs.GagentConfig{
CMode: true,
UUID: "test-uuid",
ClientPort: 1234,
Agent: tmpAgentFile.Name(),
Routers: map[string]gs.Router{
"test-router": {
RouterAddr: "127.0.0.1",
ClientPort: 1234,
},
},
}
// Replace log output with a buffer to suppress output during testing
origLogOutput := log.Writer()
defer log.SetOutput(origLogOutput)
log.SetOutput(&bytes.Buffer{})
// Replace zmq.NewSocket with a function that returns a mock socket
origNewSocket := newSocket
defer func() { newSocket = origNewSocket }()
newSocket = func(t zmq.Type) (zmq.Socket, error) {
return &mockSocket{}, nil
}
wg := &sync.WaitGroup{}
wg.Add(1)
go Main(wg, config)
wg.Wait()
}

View file

@ -1,35 +0,0 @@
package gstructs
import (
fmt "fmt"
)
type AgentStatus []string
var AgentStatuses = AgentStatus{
"ERROR",
"INIT",
"SENDING",
"RECEIVING",
"ROUTING",
"PROCESSING",
"COMPLETED",
"RETURNING",
"ERROR",
}
func (a AgentStatus) GetByIndex(index int) (string, error) {
if index < 0 || index >= len(a) {
return "", fmt.Errorf("invalid index: %d", index)
}
return a[index], nil
}
func (a AgentStatus) GetByName(name string) (byte, error) {
for i, status := range a {
if status == name {
return byte(i), nil
}
}
return 0, fmt.Errorf("value not found: %s", name)
}

View file

@ -1,59 +0,0 @@
package gstructs_test
import (
testing "testing"
gs "github.com/dragonheim/gagent/internal/gstructs"
assert "github.com/stretchr/testify/assert"
)
func TestGetByIndex(t *testing.T) {
agentStatuses := gs.AgentStatuses
tests := []struct {
index int
expected string
shouldReturn bool
}{
{0, "ERROR", true},
{1, "INIT", true},
{8, "ERROR", true},
{9, "", false},
{-1, "", false},
}
for _, test := range tests {
res, err := agentStatuses.GetByIndex(test.index)
if test.shouldReturn {
assert.NoError(t, err)
assert.Equal(t, test.expected, res)
} else {
assert.Error(t, err)
}
}
}
func TestGetByName(t *testing.T) {
agentStatuses := gs.AgentStatuses
tests := []struct {
name string
expected byte
shouldReturn bool
}{
{"ERROR", 0, true},
{"INIT", 1, true},
{"COMPLETED", 6, true},
{"INVALID", 0, false},
}
for _, test := range tests {
res, err := agentStatuses.GetByName(test.name)
if test.shouldReturn {
assert.NoError(t, err)
assert.Equal(t, test.expected, res)
} else {
assert.Error(t, err)
}
}
}

View file

@ -1,24 +1,21 @@
package gstructs package gstructs
/* // GagentConfig is the primary construct used by all modes
* GagentConfig is the primary construct used by all modes
*/
type GagentConfig struct { type GagentConfig struct {
Name string `hcl:"name,optional"` Name string `hcl:"name,optional"`
Mode string `hcl:"mode,attr"` Mode string `hcl:"mode,attr"`
UUID string `hcl:"uuid,optional"` UUID string `hcl:"uuid,optional"`
ListenAddr string `hcl:"listenaddr,optional"`
ChainDBPath string `hcl:"chaindbpath,optional"`
MonitorPort int `hcl:"monitorport,optional"` MonitorPort int `hcl:"monitorport,optional"`
ClientPort int `hcl:"clientport,optional"` ListenAddr string `hcl:"listenaddr,optional"`
RouterPort int `hcl:"routerport,optional"` ClientPort int64 `hcl:"clientport,optional"`
WorkerPort int `hcl:"workerport,optional"` RouterPort int64 `hcl:"routerport,optional"`
WorkerPort int64 `hcl:"workerport,optional"`
ChainDBPath string `hcl:"chaindbpath,optional"`
Clients []*ClientDetails `hcl:"client,block"` Clients []*ClientDetails `hcl:"client,block"`
Routers []*RouterDetails `hcl:"router,block"` Routers []*RouterDetails `hcl:"router,block"`
Workers []*WorkerDetails `hcl:"worker,block"` Workers []*WorkerDetails `hcl:"worker,block"`
Version string Version string
File string File string
Agent string
CMode bool CMode bool
} }
@ -27,14 +24,16 @@ type GagentConfig struct {
*/ */
type ClientDetails struct { type ClientDetails struct {
/* /*
* Client name for display purposes in logs and diagnostics. * Client name for display purposes in logs and
* diagnostics.
*/ */
ClientName string `hcl:",label"` ClientName string `hcl:",label"`
/* /*
* UUID String for the client node. This is used by the router to * UUID String for the client node. This is used by
* determine which MQ client to send the agent's results to. This * the router to determine which MQ client to send
* attempts to keep the clients unique globally. * the agent's results to. This attempts to keep the
* clients unique globally.
*/ */
ClientID string `hcl:"clientid,optional"` ClientID string `hcl:"clientid,optional"`
} }
@ -44,46 +43,54 @@ type ClientDetails struct {
*/ */
type RouterDetails struct { type RouterDetails struct {
/* /*
* Router name for display purposes in logs and diagnostics. * Router name for display purposes in logs and
* diagnostics
*/ */
RouterName string `hcl:",label"` RouterName string `hcl:",label"`
/* /*
* UUID String for the router node. This is used by the clients, * UUID String for the router node. This is used by
* routers, and workers to determine which MQ router to send the * the clients, routers, and workers to determine
* agent's requests to. This attempts to keep the routers unique * which MQ router to send the agent's requests to.
* globally. * This attempts to keep the routers unique globally.
*/ */
RouterID string `hcl:"routerid,attr"` RouterID string `hcl:"routerid,attr"`
/* /*
* This is the IP address or hostname the router will listen on. The * This is the IP address or hostname the router
* router will start up a 0MQ service that clients and workers will * will listen on. The router will start up a 0MQ
* connect to. * service that clients and workers will connect to.
*/ */
RouterAddr string `hcl:"address,attr"` RouterAddr string `hcl:"address,attr"`
/* /*
* These tags will be passed to the router upon connection. The router * This is the is the port that the router listens
* will then use these tags to help determine which worker / client to * on for clients. If not defined, it will default
* send the client's requests and results to. * to 35571.
*/
ClientPort int64 `hcl:"clientport,optional"`
/*
* This is the is the port that the router listens
* on for routers. If not defined, it will default
* to 35570.
*/
RouterPort int64 `hcl:"routerport,optional"`
/*
* This is the is the port that the router listens
* on for clients. If not defined, it will default
* to 35572.
*/
WorkerPort int64 `hcl:"workerport,optional"`
/*
* These tags will be passed to the router upon
* connection. The router will then use these
* tags to help determine which worker / client
* to send the client's requests and results to.
*/ */
RouterTags []string `hcl:"tags,optional"` RouterTags []string `hcl:"tags,optional"`
/*
* G'Agent client will use this port to communicate with the routers.
*/
ClientPort int `hcl:"clientport,optional"`
/*
* G'Agent router will use this port to communicate with other routers.
*/
RouterPort int `hcl:"routerport,optional"`
/*
* G'Agent worker will use this port to communicate with the routers.
*/
WorkerPort int `hcl:"workerport,optional"`
} }
/* /*
@ -91,30 +98,33 @@ type RouterDetails struct {
*/ */
type WorkerDetails struct { type WorkerDetails struct {
/* /*
* Router name for display purposes in logs and diagnostics. * Router name for display purposes in logs and
* diagnostics
*/ */
WorkerName string `hcl:",label"` WorkerName string `hcl:",label"`
/* /*
* UUID String for the worker node. This is used by the router to * UUID String for the worker node. This is used
* determine which MQ client to send agents to. This attempts to keep * by the router to determine which MQ client to
* the workers unique globally. * send agents to. This attempts to keep the
* workers unique globally.
*/ */
WorkerID string `hcl:"workerid,attr"` WorkerID string `hcl:"workerid,attr"`
/* /*
* These tags will be passed to the router upon connection. The router * These tags will be passed to the router upon
* will then use these tags to help determine which worker / client to * connection. The router will then use these
* send the agent and it's results to. * tags to help determine which worker / client
* to send the agent and it's results to.
*/ */
WorkerTags []string `hcl:"tags,optional"` WorkerTags []string `hcl:"tags,optional"`
} }
type AgentDetails struct { type AgentDetails struct {
Status byte `hcl:"status"`
Client string `hcl:"client"` Client string `hcl:"client"`
Shasum string `hcl:"shasum"` Shasum string `hcl:"shasum"`
Hints []string `hcl:"hints"` Status string `hcl:"status"`
Script []byte `hcl:"script"` ScriptCode []byte
Answer []byte `hcl:"answer"` Hints []string
Answer []byte
} }

View file

@ -1,76 +0,0 @@
package gstructs_test
import (
testing "testing"
gs "github.com/dragonheim/gagent/internal/gstructs"
)
func TestGagentConfig(t *testing.T) {
config := gs.GagentConfig{
Name: "test-config",
Mode: "client",
UUID: "test-uuid",
ListenAddr: "127.0.0.1",
ChainDBPath: "/tmp/chaindb",
MonitorPort: 8888,
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
Clients: []*gs.ClientDetails{
{
ClientName: "test-client",
ClientID: "client-id",
},
},
Routers: []*gs.RouterDetails{
{
RouterName: "test-router",
RouterID: "router-id",
RouterAddr: "192.168.1.1",
RouterTags: []string{"tag1", "tag2"},
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
},
},
Workers: []*gs.WorkerDetails{
{
WorkerName: "test-worker",
WorkerID: "worker-id",
WorkerTags: []string{"tag3", "tag4"},
},
},
Version: "1.0.0",
File: "config.hcl",
Agent: "agent.gagent",
CMode: true,
}
if config.Name != "test-config" {
t.Errorf("Expected config name to be 'test-config', got %s", config.Name)
}
if config.Mode != "client" {
t.Errorf("Expected config mode to be 'client', got %s", config.Mode)
}
// TODO: add more assertions for other config fields
}
func TestAgentDetails(t *testing.T) {
agent := gs.AgentDetails{
Status: 1,
Client: "test-client",
Shasum: "123456789abcdef",
Hints: []string{"tag1", "tag2", "tag3"},
Script: []byte("sample script content"),
Answer: []byte("sample answer content"),
}
if agent.Status != 1 {
t.Errorf("Expected agent status to be 1, got %d", agent.Status)
}
if agent.Client != "test-client" {
t.Errorf("Expected agent client to be 'test-client', got %s", agent.Client)
}
// TODO: add more assertions for other agent fields
}

View file

@ -4,46 +4,47 @@ import (
fmt "fmt" fmt "fmt"
log "log" log "log"
http "net/http" http "net/http"
strconv "strconv"
sync "sync" sync "sync"
gcdb "github.com/dragonheim/gagent/internal/chaindb" gcdb "git.dragonheim.net/dragonheim/gagent/internal/chaindb"
gstructs "github.com/dragonheim/gagent/internal/gstructs" gstructs "git.dragonheim.net/dragonheim/gagent/internal/gstructs"
prometheus "github.com/prometheus/client_golang/prometheus"
promauto "github.com/prometheus/client_golang/prometheus/promauto"
zmq "github.com/pebbe/zmq4" zmq "github.com/pebbe/zmq4"
) )
var ( var (
opsProcessed = promauto.NewCounter(prometheus.CounterOpts{
Name: "client_requests_received",
})
db gcdb.GagentDb db gcdb.GagentDb
) )
/* /*
* The 'router' processes routing requests from the agent. The router does The 'router' processes routing requests from the agent. The router does
* not handle any of the agent activities beyond processing the agent's not handle any of the agent activities beyond processing the agent's
* list of tags and passing the agent and it's storage to either a member list of tags and passing the agent and it's storage to either a member
* or client node. Tags are used by the agent to give hints as to where or client node. Tags are used by the agent to give hints as to where
* it should be routed. it should be routed.
* Main is the entrypoint for the router Main is the entrypoint for the router
*/ */
func Main(wg *sync.WaitGroup, config gstructs.GagentConfig) { func Main(wg *sync.WaitGroup, config gstructs.GagentConfig) {
log.Printf("[INFO] Starting router\n") log.Printf("[INFO] Starting router\n")
defer wg.Done() defer wg.Done()
http.HandleFunc("/hello", AnswerClient) http.HandleFunc("/hello", answerClient)
clientSock, _ := zmq.NewSocket(zmq.ROUTER) clientSock, _ := zmq.NewSocket(zmq.ROUTER)
defer clientSock.Close() defer clientSock.Close()
workerSock, _ := zmq.NewSocket(zmq.DEALER) workerSock, _ := zmq.NewSocket(zmq.DEALER)
defer workerSock.Close() defer workerSock.Close()
chain := gcdb.NewGagentDb() db.Init()
log.Println("[DEBUG] Loading chaindb ")
err := chain.LoadHCL(config.ChainDBPath)
if err != nil {
log.Printf("[ERROR] Error loading chaindb: %s", err)
}
workerListener := "tcp://" + config.ListenAddr + ":" + strconv.Itoa(config.WorkerPort) workerListener := fmt.Sprintf("tcp://%s:%d", config.ListenAddr, config.WorkerPort)
_ = workerSock.Bind(workerListener) _ = workerSock.Bind(workerListener)
workers := make([]string, 0) workers := make([]string, 0)
@ -105,23 +106,15 @@ LOOP:
/* /*
* Create listener for client requests * Create listener for client requests
*/ */
func createClientListener(wg *sync.WaitGroup, config gstructs.GagentConfig) error { func createClientListener(wg *sync.WaitGroup, config gstructs.GagentConfig) {
defer wg.Done() defer wg.Done()
clientSock, err := zmq.NewSocket(zmq.ROUTER) clientSock, _ := zmq.NewSocket(zmq.ROUTER)
if err != nil {
log.Printf("[ERROR] Error creating client socket: %s", err)
return err
}
defer clientSock.Close() defer clientSock.Close()
clientListener := "tcp://" + config.ListenAddr + ":" + strconv.Itoa(config.ClientPort) clientListener := fmt.Sprintf("tcp://%s:%d", config.ListenAddr, config.ClientPort)
log.Printf("[DEBUG] Binding to: %s", clientListener) log.Printf("[DEBUG] Binding to: %s", clientListener)
err = clientSock.Bind(clientListener) _ = clientSock.Bind(clientListener)
if err != nil {
log.Printf("[ERROR] Error binding client socket: %s", err)
return err
}
for { for {
msg, err := clientSock.RecvMessage(0) msg, err := clientSock.RecvMessage(0)
@ -130,7 +123,7 @@ func createClientListener(wg *sync.WaitGroup, config gstructs.GagentConfig) erro
} }
log.Printf("[DEBUG] Client message received: %s", msg) log.Printf("[DEBUG] Client message received: %s", msg)
} }
return nil
} }
func unwrap(msg []string) (head string, tail []string) { func unwrap(msg []string) (head string, tail []string) {
@ -143,8 +136,10 @@ func unwrap(msg []string) (head string, tail []string) {
return return
} }
func AnswerClient(w http.ResponseWriter, r *http.Request) { func answerClient(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" { if r.URL.Path != "/" {
opsProcessed.Inc()
// fmt.Fprintf(w, "%v\n", r)
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
@ -157,21 +152,18 @@ func AnswerClient(w http.ResponseWriter, r *http.Request) {
* Handle GET requests * Handle GET requests
*/ */
case http.MethodGet: case http.MethodGet:
log.Println("[DEBUG] GET method received")
fmt.Fprintf(w, "%v\n", r) fmt.Fprintf(w, "%v\n", r)
/* /*
* Handle POST requests * Handle POST requests
*/ */
case http.MethodPost: case http.MethodPost:
log.Println("[DEBUG] POST method received")
fmt.Fprintf(w, "%v\n", r) fmt.Fprintf(w, "%v\n", r)
/* /*
* Handle PUT requests * Handle PUT requests
*/ */
case http.MethodOptions: case http.MethodOptions:
log.Println("[DEBUG] PUT method received")
w.Header().Set("Allow", "GET, POST, OPTIONS") w.Header().Set("Allow", "GET, POST, OPTIONS")
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)

View file

@ -1,72 +0,0 @@
package router_test
import (
http "net/http"
httptest "net/http/httptest"
sync "sync"
testing "testing"
time "time"
gs "github.com/dragonheim/gagent/internal/gstructs"
router "github.com/dragonheim/gagent/internal/router"
)
func TestRouterMain(t *testing.T) {
config := gs.GagentConfig{
Name: "test-config",
Mode: "router",
UUID: "test-uuid",
ListenAddr: "127.0.0.1",
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
ChainDBPath: "test-chaindb-path",
}
wg := &sync.WaitGroup{}
wg.Add(1)
go router.Main(wg, config)
// Allow router to start before sending HTTP requests
time.Sleep(time.Millisecond * 100)
// Test GET request
resp := makeRequest(t, "GET", "http://localhost:1234/hello")
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode)
}
// Test POST request
resp = makeRequest(t, "POST", "http://localhost:1234/hello")
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode)
}
// Test OPTIONS request
resp = makeRequest(t, "OPTIONS", "http://localhost:1234/hello")
if resp.StatusCode != http.StatusNoContent {
t.Errorf("Expected status code %d, got %d", http.StatusNoContent, resp.StatusCode)
}
// Test unsupported method
resp = makeRequest(t, "PUT", "http://localhost:1234/hello")
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Expected status code %d, got %d", http.StatusMethodNotAllowed, resp.StatusCode)
}
wg.Wait()
}
func makeRequest(t *testing.T, method, url string) *http.Response {
req, err := http.NewRequest(method, url, nil)
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(router.AnswerClient)
handler.ServeHTTP(rr, req)
return rr.Result()
}

View file

@ -6,13 +6,13 @@ import (
cty "github.com/zclconf/go-cty/cty" cty "github.com/zclconf/go-cty/cty"
gs "github.com/dragonheim/gagent/internal/gstructs" gs "git.dragonheim.net/dragonheim/gagent/internal/gstructs"
hclwrite "github.com/hashicorp/hcl/v2/hclwrite" hclwrite "github.com/hashicorp/hcl/v2/hclwrite"
) )
/* /*
* Main is the entrypoint for the setup process Main is the entrypoint for the setup process
*/ */
func Main(wg *sync.WaitGroup, config gs.GagentConfig) { func Main(wg *sync.WaitGroup, config gs.GagentConfig) {
log.Printf("[INFO] Starting setup\n") log.Printf("[INFO] Starting setup\n")
@ -24,22 +24,20 @@ func Main(wg *sync.WaitGroup, config gs.GagentConfig) {
rootBody.SetAttributeValue("mode", cty.StringVal(config.Mode)) rootBody.SetAttributeValue("mode", cty.StringVal(config.Mode))
rootBody.SetAttributeValue("uuid", cty.StringVal(config.UUID)) rootBody.SetAttributeValue("uuid", cty.StringVal(config.UUID))
rootBody.SetAttributeValue("listenaddr", cty.StringVal("0.0.0.0")) rootBody.SetAttributeValue("listenaddr", cty.StringVal("0.0.0.0"))
rootBody.SetAttributeValue("clientport", cty.NumberIntVal(int64(config.ClientPort))) rootBody.SetAttributeValue("clientport", cty.NumberIntVal(config.ClientPort))
rootBody.SetAttributeValue("routerport", cty.NumberIntVal(int64(config.RouterPort))) rootBody.SetAttributeValue("routerport", cty.NumberIntVal(config.RouterPort))
rootBody.SetAttributeValue("workerport", cty.NumberIntVal(int64(config.WorkerPort))) rootBody.SetAttributeValue("workerport", cty.NumberIntVal(config.WorkerPort))
rootBody.AppendNewline() rootBody.AppendNewline()
clientBlock1 := rootBody.AppendNewBlock("client", []string{config.Name}) clientBlock1 := rootBody.AppendNewBlock("client", []string{config.Name})
clientBody1 := clientBlock1.Body() clientBody1 := clientBlock1.Body()
/* // clientBody1.AppendUnstructuredTokens(
* clientBody1.AppendUnstructuredTokens( // hclwrite.TokensForTraversal(hcl.Traversal{
* hclwrite.TokensForTraversal(hcl.Traversal{ // hcl.TraverseRoot{
* hcl.TraverseRoot{ // Name: hcl.CommentGenerator("comment"),
* Name: hcl.CommentGenerator("comment"), // },
* }, // },
* }, // ))
* ))
*/
clientBody1.SetAttributeValue("clientid", cty.StringVal(config.UUID)) clientBody1.SetAttributeValue("clientid", cty.StringVal(config.UUID))
rootBody.AppendNewline() rootBody.AppendNewline()
@ -47,9 +45,9 @@ func Main(wg *sync.WaitGroup, config gs.GagentConfig) {
routerBody1 := routerBlock1.Body() routerBody1 := routerBlock1.Body()
routerBody1.SetAttributeValue("routerid", cty.StringVal(config.UUID)) routerBody1.SetAttributeValue("routerid", cty.StringVal(config.UUID))
routerBody1.SetAttributeValue("address", cty.StringVal("127.0.0.1")) routerBody1.SetAttributeValue("address", cty.StringVal("127.0.0.1"))
routerBody1.SetAttributeValue("clientport", cty.NumberIntVal(int64(config.ClientPort))) routerBody1.SetAttributeValue("clientport", cty.NumberIntVal(config.ClientPort))
routerBody1.SetAttributeValue("routerport", cty.NumberIntVal(int64(config.RouterPort))) routerBody1.SetAttributeValue("routerport", cty.NumberIntVal(config.RouterPort))
routerBody1.SetAttributeValue("workerport", cty.NumberIntVal(int64(config.WorkerPort))) routerBody1.SetAttributeValue("workerport", cty.NumberIntVal(config.WorkerPort))
rootBody.AppendNewline() rootBody.AppendNewline()
workerBlock1 := rootBody.AppendNewBlock("worker", []string{config.Name}) workerBlock1 := rootBody.AppendNewBlock("worker", []string{config.Name})

View file

@ -1,57 +0,0 @@
package setup_test
import (
bytes "bytes"
io "io"
log "log"
os "os"
testing "testing"
strings "strings"
sync "sync"
gs "github.com/dragonheim/gagent/internal/gstructs"
setup "github.com/dragonheim/gagent/internal/setup"
)
func TestSetupMain(t *testing.T) {
config := gs.GagentConfig{
Name: "test-config",
Mode: "client",
UUID: "test-uuid",
ListenAddr: "127.0.0.1",
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
}
wg := &sync.WaitGroup{}
wg.Add(1)
capturedOutput := captureOutput(func() {
setup.Main(wg, config)
})
expectedOutput := `Configuration file created`
if !strings.Contains(capturedOutput, expectedOutput) {
t.Errorf("Expected output to contain '%s', got '%s'", expectedOutput, capturedOutput)
}
wg.Wait()
}
func captureOutput(f func()) string {
original := log.Writer()
r, w, _ := os.Pipe()
log.SetOutput(w)
f()
w.Close()
log.SetOutput(original)
var buf bytes.Buffer
io.Copy(&buf, r)
return buf.String()
}

View file

@ -1,15 +1,13 @@
package worker package worker
import ( import (
fmt "fmt"
log "log" log "log"
strconv "strconv"
sync "sync" sync "sync"
gstructs "github.com/dragonheim/gagent/internal/gstructs" gstructs "git.dragonheim.net/dragonheim/gagent/internal/gstructs"
/* // picol "git.dragonheim.net/dragonheim/gagent/src/picol"
* picol "github.com/dragonheim/gagent/pkg/picol"
*/
prometheus "github.com/prometheus/client_golang/prometheus" prometheus "github.com/prometheus/client_golang/prometheus"
promauto "github.com/prometheus/client_golang/prometheus/promauto" promauto "github.com/prometheus/client_golang/prometheus/promauto"
@ -24,11 +22,11 @@ var (
) )
/* /*
* The "worker" processes the agent code. The worker nodes do not know The "worker" processes the agent code. The worker nodes do not know
* anything about the network structure. Instead they know only to which anything about the network structure. Instead they know only to which
* router(s) they are connected. The worker will execute the agent code and router(s) they are connected. The worker will execute the agent code and
* pass the agent and it's results to a router. pass the agent and it's results to a router.
* Main is the entrypoint for the worker process Main is the entrypoint for the worker process
*/ */
func Main(wg *sync.WaitGroup, config gstructs.GagentConfig) { func Main(wg *sync.WaitGroup, config gstructs.GagentConfig) {
log.Printf("[INFO] Starting worker\n") log.Printf("[INFO] Starting worker\n")
@ -43,14 +41,12 @@ func Main(wg *sync.WaitGroup, config gstructs.GagentConfig) {
/* /*
* Generate connect string for this router. * Generate connect string for this router.
*/ */
connectString := "tcp://" + config.Routers[key].RouterAddr + ":" + strconv.Itoa(rport) connectString := fmt.Sprintf("tcp://%s:%d", config.Routers[key].RouterAddr, rport)
wg.Add(1) wg.Add(1)
go getAgent(wg, config.UUID, connectString) go getAgent(wg, config.UUID, connectString)
} }
/* // workerListener := fmt.Sprintf("tcp://%s:%d", config.ListenAddr, config.WorkerPort)
* workerListener := "tcp://" + config.ListenAddr + ":" + strconv.Itoa(config.WorkerPort)
*/
} }

View file

@ -1,66 +0,0 @@
package worker_test
import (
bytes "bytes"
io "io"
log "log"
os "os"
testing "testing"
strings "strings"
sync "sync"
gs "github.com/dragonheim/gagent/internal/gstructs"
worker "github.com/dragonheim/gagent/internal/worker"
)
func TestWorkerMain(t *testing.T) {
config := gs.GagentConfig{
Name: "test-config",
Mode: "worker",
UUID: "test-uuid",
ListenAddr: "127.0.0.1",
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
Routers: []*gs.RouterDetails{
{
RouterName: "test-router",
RouterID: "test-router-id",
RouterAddr: "127.0.0.1",
WorkerPort: 9012,
},
},
}
wg := &sync.WaitGroup{}
wg.Add(1)
capturedOutput := captureOutput(func() {
worker.Main(wg, config)
})
expectedOutput := `Starting worker`
if !strings.Contains(capturedOutput, expectedOutput) {
t.Errorf("Expected output to contain '%s', got '%s'", expectedOutput, capturedOutput)
}
wg.Wait()
}
func captureOutput(f func()) string {
original := log.Writer()
r, w, _ := os.Pipe()
log.SetOutput(w)
f()
w.Close()
log.SetOutput(original)
var buf bytes.Buffer
io.Copy(&buf, r)
return buf.String()
}

View file

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Lain dono <lain.dono@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,25 +1,20 @@
package picol package picol
import ( import (
errors "errors" "fmt"
fmt "fmt" "strconv"
strconv "strconv" "strings"
strings "strings" "testing"
) )
/* func arityErr(i *Interp, name string, argv []string) error {
* incorrectArgCountError returns an error message indicating the incorrect
* number of arguments provided for a given function. It takes an interpreter
* instance 'i', the function name 'name', and a slice of argument values 'argv'.
*/
func incorrectArgCountError(i *Interpreter, name string, argv []string) error {
return fmt.Errorf("wrong number of args for %s %s", name, argv) return fmt.Errorf("wrong number of args for %s %s", name, argv)
} }
/* /*
* NeedleInHaystack returns true if the string is in a slice needleInHaystack returns true if the string is in a slice
*/ */
func NeedleInHaystack(needle string, haystack []string) bool { func needleInHaystack(needle string, haystack []string) bool {
for _, haystackMember := range haystack { for _, haystackMember := range haystack {
if haystackMember == needle { if haystackMember == needle {
return true return true
@ -29,11 +24,30 @@ func NeedleInHaystack(needle string, haystack []string) bool {
} }
/* /*
* CommandMath is the math command for TCL TestneedleInHaystack tests the return value of needleInHaystack
*/ */
func CommandMath(i *Interpreter, argv []string, pd interface{}) (string, error) { func TestneedleInHaystack(t *testing.T) {
var haystack = []string{"a", "b", "c"}
var needle = "a"
if !needleInHaystack(needle, haystack) {
t.Errorf("%s not in %s", needle, haystack)
}
needle = "j"
if needleInHaystack(needle, haystack) {
t.Errorf("%s in %s", needle, haystack)
}
needle = "ab"
if needleInHaystack(needle, haystack) {
t.Errorf("%s in %s", needle, haystack)
}
}
// CommandMath is the math command for TCL
func CommandMath(i *Interp, argv []string, pd interface{}) (string, error) {
if len(argv) != 3 { if len(argv) != 3 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
a, _ := strconv.Atoi(argv[1]) a, _ := strconv.Atoi(argv[1])
b, _ := strconv.Atoi(argv[2]) b, _ := strconv.Atoi(argv[2])
@ -71,40 +85,34 @@ func CommandMath(i *Interpreter, argv []string, pd interface{}) (string, error)
if a != b { if a != b {
c = 1 c = 1
} }
default: default: // FIXME I hate warnings
return "0", errors.New("invalid operator " + argv[0]) c = 0
} }
return fmt.Sprintf("%d", c), nil return fmt.Sprintf("%d", c), nil
} }
/* // CommandSet is the set command for TCL
* CommandSet is the set command for TCL func CommandSet(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandSet(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 3 { if len(argv) != 3 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
i.SetVariable(argv[1], argv[2]) i.SetVar(argv[1], argv[2])
return argv[2], nil return argv[2], nil
} }
/* // CommandUnset is the unset command for TCL
* CommandUnset is the unset command for TCL func CommandUnset(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandUnset(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 2 { if len(argv) != 2 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
i.UnsetVariable(argv[1]) i.UnsetVar(argv[1])
return "", nil return "", nil
} }
/* // CommandIf is the if command for TCL
* CommandIf is the if command for TCL func CommandIf(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandIf(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 3 && len(argv) != 5 { if len(argv) != 3 && len(argv) != 5 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
result, err := i.Eval(argv[1]) result, err := i.Eval(argv[1])
@ -121,12 +129,10 @@ func CommandIf(i *Interpreter, argv []string, pd interface{}) (string, error) {
return result, nil return result, nil
} }
/* // CommandWhile is the while command for TCL
* CommandWhile is the while command for TCL func CommandWhile(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandWhile(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 3 { if len(argv) != 3 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
for { for {
@ -137,11 +143,9 @@ func CommandWhile(i *Interpreter, argv []string, pd interface{}) (string, error)
if r, _ := strconv.Atoi(result); r != 0 { if r, _ := strconv.Atoi(result); r != 0 {
result, err := i.Eval(argv[2]) result, err := i.Eval(argv[2])
switch err { switch err {
case ErrContinue, nil: case errContinue, nil:
/* //pass
* pass case errBreak:
*/
case ErrBreak:
return result, nil return result, nil
default: default:
return result, err return result, err
@ -152,26 +156,22 @@ func CommandWhile(i *Interpreter, argv []string, pd interface{}) (string, error)
} }
} }
/* // CommandRetCodes is a function to get the return codes for TCL
* CommandRetCodes is a function to get the return codes for TCL func CommandRetCodes(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandRetCodes(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 1 { if len(argv) != 1 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
switch argv[0] { switch argv[0] {
case "break": case "break":
return "", ErrBreak return "", errBreak
case "continue": case "continue":
return "", ErrContinue return "", errContinue
} }
return "", nil return "", nil
} }
/* // CommandCallProc is a function to call proc commands for TCL
* CommandCallProc is a function to call proc commands for TCL func CommandCallProc(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandCallProc(i *Interpreter, argv []string, pd interface{}) (string, error) {
var x []string var x []string
if pd, ok := pd.([]string); ok { if pd, ok := pd.([]string); ok {
@ -180,7 +180,7 @@ func CommandCallProc(i *Interpreter, argv []string, pd interface{}) (string, err
return "", nil return "", nil
} }
i.callframe = &CallFrame{vars: make(map[string]Variable), parent: i.callframe} i.callframe = &CallFrame{vars: make(map[string]Var), parent: i.callframe}
defer func() { i.callframe = i.callframe.parent }() // remove the called proc callframe defer func() { i.callframe = i.callframe.parent }() // remove the called proc callframe
arity := 0 arity := 0
@ -189,7 +189,7 @@ func CommandCallProc(i *Interpreter, argv []string, pd interface{}) (string, err
continue continue
} }
arity++ arity++
i.SetVariable(arg, argv[arity]) i.SetVar(arg, argv[arity])
} }
if arity != len(argv)-1 { if arity != len(argv)-1 {
@ -198,50 +198,42 @@ func CommandCallProc(i *Interpreter, argv []string, pd interface{}) (string, err
body := x[1] body := x[1]
result, err := i.Eval(body) result, err := i.Eval(body)
if err == ErrReturn { if err == errReturn {
err = nil err = nil
} }
return result, err return result, err
} }
/* // CommandProc is a function to register proc commands for TCL
* CommandProc is a function to register proc commands for TCL func CommandProc(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandProc(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 4 { if len(argv) != 4 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
return "", i.RegisterCommand(argv[1], CommandCallProc, []string{argv[2], argv[3]}) return "", i.RegisterCommand(argv[1], CommandCallProc, []string{argv[2], argv[3]})
} }
/* // CommandReturn is a function to register return codes for commands for TCL
* CommandReturn is a function to register return codes for commands for TCL func CommandReturn(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandReturn(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 1 && len(argv) != 2 { if len(argv) != 1 && len(argv) != 2 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
var r string var r string
if len(argv) == 2 { if len(argv) == 2 {
r = argv[1] r = argv[1]
} }
return r, ErrReturn return r, errReturn
} }
/* // CommandError is a function to return error codes for commands for TCL
* CommandError is a function to return error codes for commands for TCL func CommandError(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandError(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 1 && len(argv) != 2 { if len(argv) != 1 && len(argv) != 2 {
return "", incorrectArgCountError(i, argv[0], argv) return "", arityErr(i, argv[0], argv)
} }
return "", fmt.Errorf(argv[1]) return "", fmt.Errorf(argv[1])
} }
/* // CommandPuts is a function to print strings for TCL
* CommandPuts is a function to print strings for TCL func CommandPuts(i *Interp, argv []string, pd interface{}) (string, error) {
*/
func CommandPuts(i *Interpreter, argv []string, pd interface{}) (string, error) {
if len(argv) != 2 { if len(argv) != 2 {
return "", fmt.Errorf("wrong number of args for %s %s", argv[0], argv) return "", fmt.Errorf("wrong number of args for %s %s", argv[0], argv)
} }
@ -249,10 +241,8 @@ func CommandPuts(i *Interpreter, argv []string, pd interface{}) (string, error)
return "", nil return "", nil
} }
/* // RegisterCoreCommands is a callable to register TCL commands.
* RegisterCoreCommands is a callable to register TCL commands. func (i *Interp) RegisterCoreCommands() {
*/
func (i *Interpreter) RegisterCoreCommands() error {
name := [...]string{"+", "-", "*", "/", ">", ">=", "<", "<=", "==", "!="} name := [...]string{"+", "-", "*", "/", ">", ">=", "<", "<=", "==", "!="}
for _, n := range name { for _, n := range name {
_ = i.RegisterCommand(n, CommandMath, nil) _ = i.RegisterCommand(n, CommandMath, nil)
@ -267,6 +257,4 @@ func (i *Interpreter) RegisterCoreCommands() error {
_ = i.RegisterCommand("return", CommandReturn, nil) _ = i.RegisterCommand("return", CommandReturn, nil)
_ = i.RegisterCommand("error", CommandError, nil) _ = i.RegisterCommand("error", CommandError, nil)
_ = i.RegisterCommand("puts", CommandPuts, nil) _ = i.RegisterCommand("puts", CommandPuts, nil)
return nil
} }

View file

@ -1,300 +0,0 @@
package picol_test
import (
testing "testing"
picol "github.com/dragonheim/gagent/pkg/picol"
)
func Test_NeedleInHaystack(t *testing.T) {
var haystack = []string{"a", "b", "c"}
var needle = "a"
if !picol.NeedleInHaystack(needle, haystack) {
t.Errorf("%s not in %s", needle, haystack)
}
needle = "j"
if picol.NeedleInHaystack(needle, haystack) {
t.Errorf("%s in %s", needle, haystack)
}
needle = "ab"
if picol.NeedleInHaystack(needle, haystack) {
t.Errorf("%s in %s", needle, haystack)
}
}
func Test_CommandMath(t *testing.T) {
// You can add more test cases for various operations and edge cases
testCases := []struct {
argv []string
result string
err error
}{
{[]string{"+", "2", "3"}, "5", nil},
{[]string{"-", "8", "3"}, "5", nil},
{[]string{"*", "2", "3"}, "6", nil},
{[]string{"/", "6", "3"}, "2", nil},
{[]string{">", "4", "2"}, "1", nil},
}
i := picol.NewInterpreter()
for _, tc := range testCases {
result, err := picol.CommandMath(i, tc.argv, nil)
if result != tc.result || (err != nil && tc.err != nil && err.Error() != tc.err.Error()) {
t.Errorf("CommandMath(%v) = (%v, %v); expected (%v, %v)", tc.argv, result, err, tc.result, tc.err)
}
}
}
func Test_CommandSet(t *testing.T) {
testCases := []struct {
argv []string
result string
err error
}{
{[]string{"set", "x", "42"}, "42", nil},
{[]string{"set", "y", "abc"}, "abc", nil},
}
i := picol.NewInterpreter()
for _, tc := range testCases {
result, err := picol.CommandSet(i, tc.argv, nil)
if result != tc.result || (err != nil && tc.err != nil && err.Error() != tc.err.Error()) {
t.Errorf("CommandSet(%v) = (%v, %v); expected (%v, %v)", tc.argv, result, err, tc.result, tc.err)
}
}
}
func Test_CommandUnset(t *testing.T) {
testCases := []struct {
argv []string
result string
err error
}{
{[]string{"unset", "x"}, "", nil},
{[]string{"unset", "y"}, "", nil},
}
i := picol.NewInterpreter()
i.SetVariable("x", "42")
i.SetVariable("y", "abc")
for _, tc := range testCases {
result, err := picol.CommandUnset(i, tc.argv, nil)
if result != tc.result || (err != nil && tc.err != nil && err.Error() != tc.err.Error()) {
t.Errorf("CommandUnset(%v) = (%v, %v); expected (%v, %v)", tc.argv, result, err, tc.result, tc.err)
}
}
}
func Test_CommandIf(t *testing.T) {
testCases := []struct {
argv []string
result string
err error
}{
{[]string{"unset", "x"}, "", nil},
{[]string{"unset", "y"}, "", nil},
}
i := picol.NewInterpreter()
i.SetVariable("x", "42")
i.SetVariable("y", "abc")
for _, tc := range testCases {
result, err := picol.CommandUnset(i, tc.argv, nil)
if result != tc.result || (err != nil && tc.err != nil && err.Error() != tc.err.Error()) {
t.Errorf("CommandUnset(%v) = (%v, %v); expected (%v, %v)", tc.argv, result, err, tc.result, tc.err)
}
}
}
func Test_CommandWhile(t *testing.T) {
i := picol.NewInterpreter()
// Test simple while loop
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
script := `
set i 0
set result 0
while { < $i 5 } {
set result [+ $result $i]
set i [+ $i 1]
}
`
res, err := i.Eval(script)
if err != nil {
t.Fatalf("Error during while loop evaluation: %s", err)
}
expectedResult := "10"
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
// Test nested while loops
script = `
set i 0
set j 0
set result 0
while { < $i 3 } {
set j 0
while { < $j 3 } {
set result [+ $result $j]
set j [+ $j 1]
}
set i [+ $i 1]
}
`
res, err = i.Eval(script)
if err != nil {
t.Fatalf("Error during nested while loop evaluation: %s", err)
}
expectedResult = "9"
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
}
// You can also add test functions for other commands like CommandProc, CommandReturn, etc.
func Test_CommandRetCodes(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
// Test break
script := `
while { 1 } {
break
}
`
_, err = i.Eval(script)
if err != nil {
t.Fatalf("Error during break evaluation: %s", err)
}
// Test continue
script = `
set i 0
set result 0
while { < $i 5 } {
if { == $i 2 } {
continue
}
set result [+ $result $i]
set i [+ $i 1]
}
`
expectedResult := "7"
res, err := i.Eval(script)
if err != nil {
t.Fatalf("Error during continue evaluation: %s", err)
}
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
}
func Test_CommandProc(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
script := `
proc sum {a b} {
return [+ $a $b]
}
set res [sum 3 4]
`
expectedResult := "7"
res, err := i.Eval(script)
if err != nil {
t.Fatalf("Error during proc evaluation: %s", err)
}
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
}
func Test_CommandReturn(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
script := `
proc testReturn {val} {
return $val
}
set res [testReturn 42]
`
expectedResult := "42"
res, err := i.Eval(script)
if err != nil {
t.Fatalf("Error during return evaluation: %s", err)
}
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
}
func Test_CommandError(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
script := `
error "An error occurred"
`
_, err = i.Eval(script)
if err == nil || err.Error() != "An error occurred" {
t.Fatalf("Error not raised or incorrect error message: %s", err)
}
}
func Test_CommandPuts(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
// The following test checks if the "puts" command runs without any error.
// However, it doesn't check the printed output since it's not straightforward to capture stdout in tests.
script := `
puts "Hello, world!"
`
_, err = i.Eval(script)
if err != nil {
t.Fatalf("Error during puts evaluation: %s", err)
}
}
func Test_RegisterCoreCommands(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatalf("Error during core command registration: %s", err)
}
}

View file

@ -5,18 +5,16 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// Define parser token types
const ( const (
ParserTokenESC = iota ptESC = iota
ParserTokenSTR ptSTR
ParserTokenCMD ptCMD
ParserTokenVAR ptVAR
ParserTokenSEP ptSEP
ParserTokenEOL ptEOL
ParserTokenEOF ptEOF
) )
// parserStruct represents the parser state
type parserStruct struct { type parserStruct struct {
text string text string
p, start, end, ln int p, start, end, ln int
@ -24,31 +22,26 @@ type parserStruct struct {
Type int Type int
} }
// initParser initializes a new parserStruct instance func initParser(text string) *parserStruct {
func InitParser(text string) *parserStruct { return &parserStruct{text, 0, 0, 0, len(text), 0, ptEOL}
return &parserStruct{text: text, ln: len(text), Type: ParserTokenEOL}
} }
// next advances the parser position by one rune
func (p *parserStruct) next() { func (p *parserStruct) next() {
_, w := utf8.DecodeRuneInString(p.text[p.p:]) _, w := utf8.DecodeRuneInString(p.text[p.p:])
p.p += w p.p += w
p.ln -= w p.ln -= w
} }
// current returns the current rune at the parser position
func (p *parserStruct) current() rune { func (p *parserStruct) current() rune {
r, _ := utf8.DecodeRuneInString(p.text[p.p:]) r, _ := utf8.DecodeRuneInString(p.text[p.p:])
return r return r
} }
// token returns the current token text between start and end positions
func (p *parserStruct) token() (t string) { func (p *parserStruct) token() (t string) {
defer recover() defer recover()
return p.text[p.start:p.end] return p.text[p.start:p.end]
} }
// parseSep parses whitespace separators
func (p *parserStruct) parseSep() string { func (p *parserStruct) parseSep() string {
p.start = p.p p.start = p.p
for ; p.p < len(p.text); p.next() { for ; p.p < len(p.text); p.next() {
@ -57,11 +50,10 @@ func (p *parserStruct) parseSep() string {
} }
} }
p.end = p.p p.end = p.p
p.Type = ParserTokenSEP p.Type = ptSEP
return p.token() return p.token()
} }
// parseEol parses end of line and comments
func (p *parserStruct) parseEol() string { func (p *parserStruct) parseEol() string {
p.start = p.p p.start = p.p
@ -74,11 +66,10 @@ func (p *parserStruct) parseEol() string {
} }
p.end = p.p p.end = p.p
p.Type = ParserTokenEOL p.Type = ptEOL
return p.token() return p.token()
} }
// parseCommand parses a command within brackets
func (p *parserStruct) parseCommand() string { func (p *parserStruct) parseCommand() string {
level, blevel := 1, 0 level, blevel := 1, 0
p.next() // skip p.next() // skip
@ -105,20 +96,19 @@ Loop:
p.next() p.next()
} }
p.end = p.p p.end = p.p
p.Type = ParserTokenCMD p.Type = ptCMD
if p.p < len(p.text) && p.current() == ']' { if p.p < len(p.text) && p.current() == ']' {
p.next() p.next()
} }
return p.token() return p.token()
} }
// parseVar parses a variable reference
func (p *parserStruct) parseVar() string { func (p *parserStruct) parseVar() string {
p.next() // skip the $ p.next() // skip the $
p.start = p.p p.start = p.p
if p.current() == '{' { if p.current() == '{' {
p.Type = ParserTokenVAR p.Type = ptVAR
return p.parseBrace() return p.parseBrace()
} }
@ -134,15 +124,14 @@ func (p *parserStruct) parseVar() string {
if p.start == p.p { // It's just a single char string "$" if p.start == p.p { // It's just a single char string "$"
p.start = p.p - 1 p.start = p.p - 1
p.end = p.p p.end = p.p
p.Type = ParserTokenSTR p.Type = ptSTR
} else { } else {
p.end = p.p p.end = p.p
p.Type = ParserTokenVAR p.Type = ptVAR
} }
return p.token() return p.token()
} }
// parseBrace parses a brace-enclosed string
func (p *parserStruct) parseBrace() string { func (p *parserStruct) parseBrace() string {
level := 1 level := 1
p.next() // skip p.next() // skip
@ -171,12 +160,11 @@ Loop:
return p.token() return p.token()
} }
// parseString parses a string with or without quotes
func (p *parserStruct) parseString() string { func (p *parserStruct) parseString() string {
newword := p.Type == ParserTokenSEP || p.Type == ParserTokenEOL || p.Type == ParserTokenSTR newword := p.Type == ptSEP || p.Type == ptEOL || p.Type == ptSTR
if c := p.current(); newword && c == '{' { if c := p.current(); newword && c == '{' {
p.Type = ParserTokenSTR p.Type = ptSTR
return p.parseBrace() return p.parseBrace()
} else if newword && c == '"' { } else if newword && c == '"' {
p.insidequote = 1 p.insidequote = 1
@ -197,7 +185,7 @@ Loop:
case '"': case '"':
if p.insidequote != 0 { if p.insidequote != 0 {
p.end = p.p p.end = p.p
p.Type = ParserTokenESC p.Type = ptESC
p.next() p.next()
p.insidequote = 0 p.insidequote = 0
return p.token() return p.token()
@ -211,11 +199,10 @@ Loop:
} }
p.end = p.p p.end = p.p
p.Type = ParserTokenESC p.Type = ptESC
return p.token() return p.token()
} }
// parseComment skips over comment text
func (p *parserStruct) parseComment() string { func (p *parserStruct) parseComment() string {
for p.ln != 0 && p.current() != '\n' { for p.ln != 0 && p.current() != '\n' {
p.next() p.next()
@ -223,14 +210,13 @@ func (p *parserStruct) parseComment() string {
return p.token() return p.token()
} }
// GetToken returns the next token from the parser
func (p *parserStruct) GetToken() string { func (p *parserStruct) GetToken() string {
for { for {
if p.ln == 0 { if p.ln == 0 {
if p.Type != ParserTokenEOL && p.Type != ParserTokenEOF { if p.Type != ptEOL && p.Type != ptEOF {
p.Type = ParserTokenEOL p.Type = ptEOL
} else { } else {
p.Type = ParserTokenEOF p.Type = ptEOF
} }
return p.token() return p.token()
} }
@ -251,7 +237,7 @@ func (p *parserStruct) GetToken() string {
case '$': case '$':
return p.parseVar() return p.parseVar()
case '#': case '#':
if p.Type == ParserTokenEOL { if p.Type == ptEOL {
p.parseComment() p.parseComment()
continue continue
} }
@ -260,4 +246,5 @@ func (p *parserStruct) GetToken() string {
return p.parseString() return p.parseString()
} }
} }
/* return p.token() /* unreached */
} }

View file

@ -1,79 +0,0 @@
package picol_test
import (
"testing"
"github.com/dragonheim/gagent/pkg/picol"
)
func TestParser(t *testing.T) {
testCases := []struct {
name string
input string
expected []int
}{
{
"Simple test",
"set x 10\nincr x",
[]int{
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenEOL,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenEOL,
picol.ParserTokenEOF,
},
},
{
"Variable and command test",
"set x $y\nputs [expr $x * 2]",
[]int{
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenVAR,
picol.ParserTokenEOL,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenCMD,
picol.ParserTokenEOL,
picol.ParserTokenEOF,
},
},
{
"Braces and quotes test",
`set x {"Hello World"}`,
[]int{
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenEOL,
picol.ParserTokenEOF,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
parser := picol.InitParser(tc.input)
for _, expectedType := range tc.expected {
parser.GetToken()
if parser.Type != expectedType {
t.Errorf("Expected token type %d, got %d", expectedType, parser.Type)
}
if parser.Type == picol.ParserTokenEOF {
break
}
}
})
}
}

View file

@ -6,66 +6,38 @@ import (
"strings" "strings"
) )
/*
* Error variables
*/
var ( var (
ErrReturn = errors.New("RETURN") errReturn = errors.New("RETURN")
ErrBreak = errors.New("BREAK") errBreak = errors.New("BREAK")
ErrContinue = errors.New("CONTINUE") errContinue = errors.New("CONTINUE")
) )
/* type Var string
* Variable type type CmdFunc func(i *Interp, argv []string, privdata interface{}) (string, error)
*/ type Cmd struct {
type Variable string fn CmdFunc
/*
* CommandFunc type
*/
type CommandFunc func(interp *Interpreter, argv []string, privdata interface{}) (string, error)
/*
* Command structure
*/
type Command struct {
fn CommandFunc
privdata interface{} privdata interface{}
} }
/*
* CallFrame structure
*/
type CallFrame struct { type CallFrame struct {
vars map[string]Variable vars map[string]Var
parent *CallFrame parent *CallFrame
} }
type Interp struct {
/*
* Interpreter structure
*/
type Interpreter struct {
level int level int
callframe *CallFrame callframe *CallFrame
commands map[string]Command commands map[string]Cmd
} }
/* func InitInterp() *Interp {
* NewInterpreter initializes a new Interpreter return &Interp{
*/
func NewInterpreter() *Interpreter {
return &Interpreter{
level: 0, level: 0,
callframe: &CallFrame{vars: make(map[string]Variable)}, callframe: &CallFrame{vars: make(map[string]Var)},
commands: make(map[string]Command), commands: make(map[string]Cmd),
} }
} }
/* func (i *Interp) Var(name string) (Var, bool) {
* Variable retrieves a variable's value for frame := i.callframe; frame != nil; frame = frame.parent {
*/
func (interp *Interpreter) Variable(name string) (Variable, bool) {
for frame := interp.callframe; frame != nil; frame = frame.parent {
v, ok := frame.vars[name] v, ok := frame.vars[name]
if ok { if ok {
return v, ok return v, ok
@ -73,112 +45,94 @@ func (interp *Interpreter) Variable(name string) (Variable, bool) {
} }
return "", false return "", false
} }
func (i *Interp) SetVar(name, val string) {
/* i.callframe.vars[name] = Var(val)
* SetVariable sets a variable's value
*/
func (interp *Interpreter) SetVariable(name, val string) {
interp.callframe.vars[name] = Variable(val)
} }
/* func (i *Interp) UnsetVar(name string) {
* UnsetVariable removes a variable delete(i.callframe.vars, name)
*/
func (interp *Interpreter) UnsetVariable(name string) {
delete(interp.callframe.vars, name)
} }
/* func (i *Interp) Command(name string) *Cmd {
* Command retrieves a command v, ok := i.commands[name]
*/
func (interp *Interpreter) Command(name string) *Command {
v, ok := interp.commands[name]
if !ok { if !ok {
return nil return nil
} }
return &v return &v
} }
/* func (i *Interp) RegisterCommand(name string, fn CmdFunc, privdata interface{}) error {
* RegisterCommand registers a new command c := i.Command(name)
*/ if c != nil {
func (interp *Interpreter) RegisterCommand(name string, fn CommandFunc, privdata interface{}) error {
cmd := interp.Command(name)
if cmd != nil {
return fmt.Errorf("Command '%s' already defined", name) return fmt.Errorf("Command '%s' already defined", name)
} }
interp.commands[name] = Command{fn, privdata} i.commands[name] = Cmd{fn, privdata}
return nil return nil
} }
/* /* EVAL! */
* Eval evaluates a script func (i *Interp) Eval(t string) (string, error) {
*/ p := initParser(t)
func (interp *Interpreter) Eval(script string) (string, error) {
parser := InitParser(script)
var result string var result string
var err error var err error
argv := []string{} argv := []string{}
for { for {
prevType := parser.Type prevtype := p.Type
token := parser.GetToken() // XXX
if parser.Type == ParserTokenEOF { t = p.GetToken()
if p.Type == ptEOF {
break break
} }
switch parser.Type { switch p.Type {
case ParserTokenVAR: case ptVAR:
v, ok := interp.Variable(token) v, ok := i.Var(t)
if !ok { if !ok {
return "", fmt.Errorf("no such variable '%s'", token) return "", fmt.Errorf("no such variable '%s'", t)
} }
token = string(v) t = string(v)
case ParserTokenCMD: case ptCMD:
result, err = interp.Eval(token) result, err = i.Eval(t)
if err != nil { if err != nil {
return result, err return result, err
} else {
t = result
} }
token = result case ptESC:
case ParserTokenESC: // XXX: escape handling missing!
/* case ptSEP:
* TODO: escape handling missing! prevtype = p.Type
*/
case ParserTokenSEP:
// prevType = parser.Type
continue continue
} }
if parser.Type == ParserTokenEOL { // We have a complete command + args. Call it!
// prevType = parser.Type if p.Type == ptEOL {
prevtype = p.Type
if len(argv) != 0 { if len(argv) != 0 {
cmd := interp.Command(argv[0]) c := i.Command(argv[0])
if cmd == nil { if c == nil {
return "", fmt.Errorf("no such command '%s'", argv[0]) return "", fmt.Errorf("no such command '%s'", argv[0])
} }
result, err = cmd.fn(interp, argv, cmd.privdata) result, err = c.fn(i, argv, c.privdata)
if err != nil { if err != nil {
return result, err return result, err
} }
} }
/* // Prepare for the next command
* Prepare for the next command
*/
argv = []string{} argv = []string{}
continue continue
} }
/* // We have a new token, append to the previous or as new arg?
* We have a new token, append to the previous or as new arg? if prevtype == ptSEP || prevtype == ptEOL {
*/ argv = append(argv, t)
if prevType == ParserTokenSEP || prevType == ParserTokenEOL {
argv = append(argv, token)
} else { // Interpolation } else { // Interpolation
argv[len(argv)-1] = strings.Join([]string{argv[len(argv)-1], token}, "") argv[len(argv)-1] = strings.Join([]string{argv[len(argv)-1], t}, "")
} }
// prevType = parser.Type prevtype = p.Type
} }
return result, nil return result, nil
} }

View file

@ -1,56 +0,0 @@
package picol_test
import (
"testing"
"github.com/dragonheim/gagent/pkg/picol"
)
func TestInterpreter(t *testing.T) {
interp := picol.NewInterpreter()
// Register a command
err := interp.RegisterCommand("test", testCommand, nil)
if err != nil {
t.Fatalf("Error registering test command: %v", err)
}
// Test command execution
script := "test hello world"
result, err := interp.Eval(script)
if err != nil {
t.Fatalf("Error executing script: %v", err)
}
expected := "hello world"
if result != expected {
t.Errorf("Expected result '%s', got '%s'", expected, result)
}
// Test variable setting
interp.SetVariable("x", "42")
// Test variable retrieval
val, ok := interp.Variable("x")
if !ok {
t.Fatalf("Variable 'x' not found")
}
expectedVar := "42"
if val != picol.Variable(expectedVar) {
t.Errorf("Expected variable value '%s', got '%s'", expectedVar, val)
}
// Test variable unsetting
interp.UnsetVariable("x")
_, ok = interp.Variable("x")
if ok {
t.Fatalf("Variable 'x' should have been unset")
}
}
// testCommand is a simple custom command for testing
func testCommand(interp *picol.Interpreter, argv []string, privdata interface{}) (string, error) {
if len(argv) != 3 {
return "", nil
}
return argv[1] + " " + argv[2], nil
}

View file

@ -1,26 +1,37 @@
package main package main
import ( import (
bufio "bufio" "bufio"
flag "flag" "flag"
fmt "fmt" "fmt"
io "io" "io/ioutil"
os "os" "os"
picol "github.com/dragonheim/gagent/pkg/picol" picol "git.dragonheim.net/dragonheim/gagent/src/picol"
) )
var fname = flag.String("f", "", "file name") var fname = flag.String("f", "", "file name")
func RunPicol(fname string) error { // CommandPuts is a simple version of the TCL puts function.
interp := picol.NewInterpreter() func CommandPuts(i *picol.Interp, argv []string, pd interface{}) (string, error) {
interp.RegisterCoreCommands() if len(argv) != 2 {
return "", fmt.Errorf("wrong number of args for %s %s", argv[0], argv)
}
fmt.Println(argv[1])
return "", nil
}
buf, err := io.ReadFile(fname) func main() {
flag.Parse()
interp := picol.InitInterp()
interp.RegisterCoreCommands()
interp.RegisterCommand("puts", CommandPuts, nil)
buf, err := ioutil.ReadFile(*fname)
if err == nil { if err == nil {
result, err := interp.Eval(string(buf)) result, err := interp.Eval(string(buf))
if err != nil { if err != nil {
return fmt.Errorf("Error: %s, Result: %s", err, result) fmt.Println("ERRROR", result, err)
} }
} else { } else {
for { for {
@ -29,17 +40,8 @@ func RunPicol(fname string) error {
clibuf, _ := scanner.ReadString('\n') clibuf, _ := scanner.ReadString('\n')
result, err := interp.Eval(clibuf[:len(clibuf)-1]) result, err := interp.Eval(clibuf[:len(clibuf)-1])
if len(result) != 0 { if len(result) != 0 {
return fmt.Errorf("Error: %s, Result: %s", err, result) fmt.Println("ERRROR", result, err)
} }
} }
} }
return nil
}
func main() {
flag.Parse()
err := RunPicol(*fname)
if err != nil {
fmt.Println(err)
}
} }

View file

@ -1,31 +0,0 @@
package main_test
import (
io "io"
os "os"
testing "testing"
picol "github.com/dragonheim/gagent/pkg/picol/picol_unused"
)
func Test_RunPicol(t *testing.T) {
// Create a temporary test file
content := []byte("set a 5\nset b 7\n+ $a $b\n")
tmpfile, err := io.TempFile("", "picol_test")
if err != nil {
t.Fatalf("Error creating temporary test file: %v", err)
}
defer os.Remove(tmpfile.Name()) // clean up
if _, err := tmpfile.Write(content); err != nil {
t.Fatalf("Error writing content to temporary test file: %v", err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("Error closing temporary test file: %v", err)
}
err = picol.RunPicol(tmpfile.Name())
if err != nil {
t.Errorf("Error during RunPicol: %v", err)
}
}