mirror of
https://codeberg.org/scip/ephemerup.git
synced 2025-12-16 20:20:58 +01:00
mv to codeberg
This commit is contained in:
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[bug-report]"
|
||||
labels: bug
|
||||
assignees: TLINDEN
|
||||
|
||||
---
|
||||
|
||||
**Describtion**
|
||||
<!-- Please provide a clear and concise description of the issue: -->
|
||||
|
||||
|
||||
**Steps To Reproduce**
|
||||
<!-- Please detail the steps to reproduce the behavior: -->
|
||||
|
||||
|
||||
**Expected behavior**
|
||||
<!-- What do you expected to happen instead? -->
|
||||
|
||||
|
||||
**Version information**
|
||||
<!--
|
||||
Please provide as much version information as possible:
|
||||
- if you have just installed a binary, provide the output of: tablizer --version
|
||||
- if you installed from source, provide the output of: make show-version
|
||||
- provide additional details: operating system and version and shell environment
|
||||
-->
|
||||
|
||||
|
||||
**Additional informations**
|
||||
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature
|
||||
title: "[feature-request]"
|
||||
labels: feature-request
|
||||
assignees: TLINDEN
|
||||
|
||||
---
|
||||
|
||||
**Describtion**
|
||||
<!-- Please provide a clear and concise description of the feature you desire: -->
|
||||
|
||||
|
||||
|
||||
**Version information**
|
||||
<!--
|
||||
Just in case the feature is already present, please provide as
|
||||
much version information as possible:
|
||||
- if you have just installed a binary, provide the output of: tablizer --version
|
||||
- if you installed from source, provide the output of: make show-version
|
||||
- provide additional details: operating system and version and shell environment
|
||||
-->
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
gitea_urls:
|
||||
api: https://codeberg.org/api/v1
|
||||
download: https://codeberg.org
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- freebsd
|
||||
|
||||
archives:
|
||||
- formats: [tar.gz]
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}_{{ .Tag }}
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
- goos: linux
|
||||
formats: [tar.gz,binary]
|
||||
files:
|
||||
- src: "*.md"
|
||||
strip_parent: true
|
||||
- src: Makefile.dist
|
||||
dst: Makefile
|
||||
wrap_in_directory: true
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
groups:
|
||||
- title: Improved
|
||||
regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$'
|
||||
order: 0
|
||||
- title: Fixed
|
||||
regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: Changed
|
||||
order: 999
|
||||
|
||||
release:
|
||||
header: "# Release Notes"
|
||||
footer: >-
|
||||
|
||||
---
|
||||
|
||||
Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/ephemerup/compare/{{ .PreviousTag }}...{{ .Tag }})
|
||||
@@ -1,28 +0,0 @@
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
goversion:
|
||||
- 1.24
|
||||
|
||||
labels:
|
||||
platform: ${platform}
|
||||
|
||||
steps:
|
||||
build:
|
||||
when:
|
||||
event: [push]
|
||||
image: golang:${goversion}
|
||||
commands:
|
||||
- go get
|
||||
- go build
|
||||
|
||||
test:
|
||||
when:
|
||||
event: [push]
|
||||
image: golang:${goversion}
|
||||
commands:
|
||||
- go get
|
||||
- go test -v -cover ./...
|
||||
- cd upctl
|
||||
- go test -v -cover ./...
|
||||
depends_on: [build]
|
||||
@@ -1,32 +0,0 @@
|
||||
# https://woodpecker-ci.org/plugins/docker-buildx
|
||||
# enable Package unit and go to /scip/-/packages after building to link to proj
|
||||
|
||||
variables:
|
||||
- &repo codeberg.org/${CI_REPO_OWNER}/ephemerup
|
||||
|
||||
steps:
|
||||
dryrun:
|
||||
image: docker.io/woodpeckerci/plugin-docker-buildx:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile
|
||||
platforms: linux/amd64
|
||||
dry_run: true
|
||||
repo: *repo
|
||||
tags: latest
|
||||
when:
|
||||
event: [push,manual]
|
||||
|
||||
publish:
|
||||
image: docker.io/woodpeckerci/plugin-docker-buildx:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile
|
||||
platforms: linux/amd64
|
||||
repo: *repo
|
||||
registry: codeberg.org
|
||||
tags: latest,${CI_COMMIT_SHA:0:8},${CI_COMMIT_TAG}
|
||||
username: ${CI_REPO_OWNER}
|
||||
password:
|
||||
from_secret: REGISTRY_TOKEN
|
||||
when:
|
||||
event: [tag,manual]
|
||||
branch: main
|
||||
@@ -1,15 +0,0 @@
|
||||
# build release
|
||||
|
||||
labels:
|
||||
platform: linux/amd64
|
||||
|
||||
steps:
|
||||
goreleaser:
|
||||
image: goreleaser/goreleaser
|
||||
when:
|
||||
event: [tag]
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: DEPLOY_TOKEN
|
||||
commands:
|
||||
- goreleaser release --clean --verbose
|
||||
33
Dockerfile
33
Dockerfile
@@ -1,33 +0,0 @@
|
||||
# syntax=docker/dockerfile:1 -*-python-*-
|
||||
FROM golang:1.24-alpine as builder
|
||||
|
||||
RUN apk update
|
||||
RUN apk upgrade
|
||||
RUN apk add --no-cache git make bash binutils
|
||||
|
||||
RUN git --version
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
COPY go.mod .
|
||||
COPY . .
|
||||
RUN go mod download
|
||||
RUN make && strip ephemerupd
|
||||
|
||||
FROM alpine:3.22
|
||||
LABEL maintainer="Uploads Author <info@daemon.de>"
|
||||
|
||||
RUN install -o 1001 -g 1001 -d /data
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /work/ephemerupd /app/ephemerupd
|
||||
|
||||
ENV EPHEMERUPD_LISTEN=:8080
|
||||
ENV EPHEMERUPD_STORAGEDIR=/data
|
||||
ENV EPHEMERUPD_DBFILE=/data/bbolt.db
|
||||
ENV EPHEMERUPD_DEBUG=1
|
||||
|
||||
USER 1001:1001
|
||||
EXPOSE 8080
|
||||
VOLUME /data
|
||||
CMD ["/app/ephemerupd"]
|
||||
106
Makefile
106
Makefile
@@ -1,106 +0,0 @@
|
||||
|
||||
# Copyright © 2023 Thomas von Dein
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#
|
||||
# no need to modify anything below
|
||||
version = $(shell egrep "= .v" cfg/config.go | cut -d'=' -f2 | cut -d'"' -f 2)
|
||||
archs = android darwin freebsd linux netbsd openbsd windows
|
||||
PREFIX = /usr/local
|
||||
UID = root
|
||||
GID = 0
|
||||
BRANCH = $(shell git branch --show-current)
|
||||
COMMIT = $(shell git rev-parse --short=8 HEAD)
|
||||
BUILD = $(shell date +%Y.%m.%d.%H%M%S)
|
||||
VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version))
|
||||
BR_MAIN := $(if $(filter $(BRANCH), main),"main","")
|
||||
HAVE_POD := $(shell pod2text -h 2>/dev/null)
|
||||
HAVE_LINT:= $(shell golangci-lint -h 2>/dev/null)
|
||||
DAEMON := ephemerupd
|
||||
CLIENT := upctl
|
||||
DATE = $(shell date +%Y-%m-%d)
|
||||
|
||||
|
||||
all: cmd/formtemplate.go lint buildlocal buildlocalctl
|
||||
|
||||
lint:
|
||||
ifdef HAVE_LINT
|
||||
golangci-lint run
|
||||
endif
|
||||
|
||||
buildlocalctl:
|
||||
make -C upctl
|
||||
|
||||
buildlocal:
|
||||
# go build -ldflags "-X 'codeberg.org/scip/ephemerup/cfg.VERSION=$(VERSION)'" -o $(DAEMON)
|
||||
CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags "-extldflags=-static -s -X 'codeberg.org/scip/ephemerup/cfg.VERSION=$(VERSION)'" -o $(DAEMON)
|
||||
|
||||
buildimage: clean
|
||||
docker-compose --verbose build
|
||||
|
||||
release:
|
||||
ifdef BR_MAIN
|
||||
git tag -a $(version) -m "$(version) released on $(DATE)"
|
||||
git push origin --tags
|
||||
./mkrel.sh $(DAEMON) $(CLIENT) $(version)
|
||||
gh release create $(version) --generate-notes releases/*
|
||||
else
|
||||
@echo "Cannot create release on branch $(BRANCH), checkout main and retry!"
|
||||
endif
|
||||
|
||||
install: buildlocal
|
||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
||||
install -o $(UID) -g $(GID) -m 555 $(DAEMON) $(PREFIX)/sbin/
|
||||
|
||||
cleanctl:
|
||||
make -C upctl clean
|
||||
|
||||
clean: cleanctl
|
||||
rm -rf releases coverage.out $(DAEMON)
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
# bash t/test.sh
|
||||
|
||||
singletest:
|
||||
@echo "Call like this: ''make singletest TEST=TestX1 MOD=lib"
|
||||
go test -run $(TEST) codeberg.org/scip/ephemerup/$(MOD)
|
||||
|
||||
cover-report:
|
||||
go test ./... -cover -coverprofile=coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
show-versions: buildlocal
|
||||
@echo "### ephemerupd version:"
|
||||
@./ephemerupd --version
|
||||
|
||||
@echo
|
||||
@echo "### go module versions:"
|
||||
@go list -m all
|
||||
|
||||
@echo
|
||||
@echo "### go version used for building:"
|
||||
@grep -m 1 go go.mod
|
||||
|
||||
goupdate:
|
||||
go get -t -u=patch ./...
|
||||
|
||||
cmd/%.go: templates/%.html
|
||||
echo "package cmd" > cmd/$*.go
|
||||
echo >> cmd/$*.go
|
||||
echo "const formtemplate = \`" >> cmd/$*.go
|
||||
cat templates/$*.html >> cmd/$*.go
|
||||
echo "\`" >> cmd/$*.go
|
||||
@@ -1,18 +0,0 @@
|
||||
# -*-make-*-
|
||||
|
||||
.PHONY: install all
|
||||
|
||||
tool = rpn
|
||||
PREFIX = /usr/local
|
||||
UID = root
|
||||
GID = 0
|
||||
|
||||
all:
|
||||
@echo "Type 'sudo make install' to install the tool."
|
||||
@echo "To change prefix, type 'sudo make install PREFIX=/opt'"
|
||||
|
||||
install:
|
||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
||||
install -d -o $(UID) -g $(GID) $(PREFIX)/share/doc
|
||||
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
|
||||
install -o $(UID) -g $(GID) -m 444 *.md $(PREFIX)/share/doc/
|
||||
@@ -2,8 +2,11 @@
|
||||
[](https://codeberg.org/scip/ephemerup/raw/branch/main/LICENSE)
|
||||
[](https://goreportcard.com/report/codeberg.org/scip/ephemerup)
|
||||
|
||||
# ephemerup
|
||||
Simple standalone file upload server with expiration and commandline client.
|
||||
# ephemerup - Simple standalone file upload server with expiration and commandline client.
|
||||
|
||||
> [!CAUTION]
|
||||
> This app is now being maintained on [Codeberg](https://codeberg.org/scip/ephemerup/).
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
128
api/auth.go
128
api/auth.go
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/keyauth/v2"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
)
|
||||
|
||||
// these vars can be savely global, since they don't change ever
|
||||
var (
|
||||
errMissing = &fiber.Error{
|
||||
Code: 403000,
|
||||
Message: "Missing API key",
|
||||
}
|
||||
|
||||
errInvalid = &fiber.Error{
|
||||
Code: 403001,
|
||||
Message: "Invalid API key",
|
||||
}
|
||||
|
||||
Apikeys []cfg.Apicontext
|
||||
)
|
||||
|
||||
// fill from server: accepted keys
|
||||
func AuthSetApikeys(keys []cfg.Apicontext) {
|
||||
Apikeys = keys
|
||||
}
|
||||
|
||||
// make sure we always return JSON encoded errors
|
||||
func AuthErrHandler(ctx *fiber.Ctx, err error) error {
|
||||
ctx.Status(fiber.StatusForbidden)
|
||||
|
||||
if err == errMissing {
|
||||
return ctx.JSON(errMissing)
|
||||
}
|
||||
|
||||
return ctx.JSON(errInvalid)
|
||||
}
|
||||
|
||||
// validator hook, validates incoming api key against form id, which
|
||||
// also acts as onetime api key
|
||||
func AuthValidateOnetimeKey(c *fiber.Ctx, key string, db *Db) (bool, error) {
|
||||
resp, err := db.Get("", key, common.TypeForm)
|
||||
if err != nil {
|
||||
return false, errors.New("Onetime key doesn't match any form id!")
|
||||
}
|
||||
|
||||
if len(resp.Forms) != 1 {
|
||||
return false, errors.New("db.Get(form) returned no results and no errors!")
|
||||
}
|
||||
|
||||
sess, err := Sessionstore.Get(c)
|
||||
if err != nil {
|
||||
return false, errors.New("Could not retrieve session from Sessionstore: " + err.Error())
|
||||
}
|
||||
|
||||
// store the result into the session, the 'formid' key tells the
|
||||
// upload handler that the apicontext it sees is in fact a form id
|
||||
// and has to be deleted if set to asap.
|
||||
sess.Set("apicontext", resp.Forms[0].Context)
|
||||
sess.Set("formid", key)
|
||||
|
||||
if err := sess.Save(); err != nil {
|
||||
return false, errors.New("Unable to save session store!")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// validator hook, called by fiber via server keyauth.New()
|
||||
func AuthValidateAPIKey(c *fiber.Ctx, key string) (bool, error) {
|
||||
// create a new session, it will be thrown away if something fails
|
||||
sess, err := Sessionstore.Get(c)
|
||||
if err != nil {
|
||||
return false, errors.New("Unable to initialize session store from context!")
|
||||
}
|
||||
|
||||
// if Apikeys is empty, the server works unauthenticated
|
||||
// FIXME: maybe always reject?
|
||||
if len(Apikeys) == 0 {
|
||||
sess.Set("apicontext", "default")
|
||||
|
||||
if err := sess.Save(); err != nil {
|
||||
return false, errors.New("Unable to save session store!")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// actual key comparison
|
||||
for _, apicontext := range Apikeys {
|
||||
hashedAPIKey := sha256.Sum256([]byte(apicontext.Key))
|
||||
hashedKey := sha256.Sum256([]byte(key))
|
||||
|
||||
if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 {
|
||||
// apikey matches, register apicontext for later use by the handlers
|
||||
sess.Set("apicontext", apicontext.Context)
|
||||
|
||||
if err := sess.Save(); err != nil {
|
||||
return false, errors.New("Unable to save session store!")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, keyauth.ErrMissingOrMalformedAPIKey
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
//"github.com/alecthomas/repr"
|
||||
"encoding/json"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DeleteExpiredUploads(conf *cfg.Config, db *Db) error {
|
||||
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(Bucket))
|
||||
|
||||
if bucket == nil {
|
||||
return nil // nothin to delete so far
|
||||
}
|
||||
|
||||
err := bucket.ForEach(func(id, j []byte) error {
|
||||
upload := &common.Upload{}
|
||||
if err := json.Unmarshal(j, &upload); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
if IsExpired(conf, upload.Created.Time, upload.Expire) {
|
||||
if err := bucket.Delete([]byte(id)); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cleanup(filepath.Join(conf.StorageDir, upload.Id))
|
||||
|
||||
Log("Cleaned up upload " + upload.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func BackgroundCleaner(conf *cfg.Config, db *Db) chan bool {
|
||||
ticker := time.NewTicker(conf.CleanInterval)
|
||||
fmt.Println(conf.CleanInterval)
|
||||
done := make(chan bool)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := DeleteExpiredUploads(conf, db); err != nil {
|
||||
Log("Failed to delete eypired uploads: %s", err.Error())
|
||||
}
|
||||
case <-done:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return done
|
||||
}
|
||||
233
api/db.go
233
api/db.go
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
//"github.com/alecthomas/repr"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const Bucket string = "data"
|
||||
|
||||
// wrapper for bolt db
|
||||
type Db struct {
|
||||
bolt *bolt.DB
|
||||
cfg *cfg.Config
|
||||
}
|
||||
|
||||
func NewDb(c *cfg.Config) (*Db, error) {
|
||||
b, err := bolt.Open(c.DbFile, 0600, nil)
|
||||
db := Db{bolt: b, cfg: c}
|
||||
return &db, err
|
||||
}
|
||||
|
||||
func (db *Db) Close() {
|
||||
db.bolt.Close()
|
||||
}
|
||||
|
||||
func (db *Db) Insert(id string, entry common.Dbentry) error {
|
||||
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte(Bucket))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create bucket: %s", err)
|
||||
}
|
||||
|
||||
jsonentry, err := entry.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("json marshalling failure: %s", err)
|
||||
}
|
||||
|
||||
err = bucket.Put([]byte(id), []byte(jsonentry))
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert data: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
Log("DB error: %s", err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Db) Delete(apicontext string, id string) error {
|
||||
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(Bucket))
|
||||
|
||||
if bucket == nil {
|
||||
return fmt.Errorf("id %s not found", id)
|
||||
}
|
||||
|
||||
j := bucket.Get([]byte(id))
|
||||
|
||||
if len(j) == 0 {
|
||||
return fmt.Errorf("id %s not found", id)
|
||||
}
|
||||
|
||||
entryContext, err := common.GetContext(j)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
if (apicontext != "" && (db.cfg.Super == apicontext || entryContext == apicontext)) || apicontext == "" {
|
||||
return bucket.Delete([]byte(id))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
Log("DB error: %s", err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Db) List(apicontext string, filter string, query string, t int) (*common.Response, error) {
|
||||
response := &common.Response{}
|
||||
qr := regexp.MustCompile(query)
|
||||
|
||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(Bucket))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := bucket.ForEach(func(id, j []byte) error {
|
||||
allowed := false
|
||||
entry, err := common.Unmarshal(j, t)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
if !entry.IsType(t) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var entryContext string
|
||||
if t == common.TypeUpload {
|
||||
entryContext = entry.(*common.Upload).Context
|
||||
} else {
|
||||
entryContext = entry.(*common.Form).Context
|
||||
}
|
||||
|
||||
// check if the user is allowed to list this entry
|
||||
if apicontext != "" && db.cfg.Super != apicontext {
|
||||
// authenticated user but not member of super
|
||||
// only return the uploads matching her context
|
||||
if apicontext == entryContext {
|
||||
// unless a filter OR no filter specified
|
||||
if (filter != "" && entryContext == filter) || filter == "" {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// return all, because we operate a public service or current==super
|
||||
if (filter != "" && entryContext == filter) || filter == "" {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
|
||||
if allowed {
|
||||
// user is allowed to view this entry, check if she also wants to see it
|
||||
if query != "" {
|
||||
if entry.MatchDescription(qr) ||
|
||||
entry.MatchExpire(qr) ||
|
||||
entry.MatchCreated(qr) ||
|
||||
entry.MatchFile(qr) {
|
||||
allowed = true
|
||||
} else {
|
||||
allowed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if allowed {
|
||||
// ok, legit and wanted
|
||||
response.Append(entry)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err // might be nil as well
|
||||
})
|
||||
|
||||
return response, err
|
||||
}
|
||||
|
||||
// we only return one obj here, but could return more later
|
||||
// FIXME: turn the id into a filter and call (Uploads|Forms)List(), same code!
|
||||
func (db *Db) Get(apicontext string, id string, t int) (*common.Response, error) {
|
||||
response := &common.Response{}
|
||||
|
||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(Bucket))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
j := bucket.Get([]byte(id))
|
||||
if j == nil {
|
||||
return fmt.Errorf("No upload object found with id %s", id)
|
||||
}
|
||||
|
||||
entry, err := common.Unmarshal(j, t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
var entryContext string
|
||||
if t == common.TypeUpload {
|
||||
entryContext = entry.(*common.Upload).Context
|
||||
} else {
|
||||
entryContext = entry.(*common.Form).Context
|
||||
}
|
||||
|
||||
if (apicontext != "" && (db.cfg.Super == apicontext || entryContext == apicontext)) || apicontext == "" {
|
||||
// allowed if no context (public or download)
|
||||
// or if context matches or if context==super
|
||||
response.Append(entry)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return response, err
|
||||
}
|
||||
|
||||
// a wrapper around Lookup() which extracts the 1st upload, if any
|
||||
func (db *Db) Lookup(apicontext string, id string, t int) (*common.Response, error) {
|
||||
response, err := db.Get(apicontext, id, t)
|
||||
|
||||
if err != nil {
|
||||
// non existent db entry with that id, or other db error, see logs
|
||||
return &common.Response{}, fmt.Errorf("No upload object found with id %s", id)
|
||||
}
|
||||
|
||||
if len(response.Uploads) == 0 {
|
||||
return &common.Response{}, fmt.Errorf("No upload object found with id %s", id)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
239
api/db_test.go
239
api/db_test.go
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
//"github.com/alecthomas/repr"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
"github.com/maxatome/go-testdeep/td"
|
||||
)
|
||||
|
||||
func finalize(db *Db) {
|
||||
if db.bolt != nil {
|
||||
db.Close()
|
||||
}
|
||||
if _, err := os.Stat(db.cfg.DbFile); err == nil {
|
||||
os.Remove(db.cfg.DbFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
dbfile string
|
||||
wantfail bool
|
||||
}{
|
||||
{"opennew", "test.db", false},
|
||||
{"openfail", "/hopefully/not/existing/directory/test.db", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
c := &cfg.Config{DbFile: tt.dbfile}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db, err := NewDb(c)
|
||||
defer finalize(db)
|
||||
if err != nil && !tt.wantfail {
|
||||
t.Error("expected: &Db{}, got err: " + err.Error())
|
||||
}
|
||||
|
||||
if err == nil && tt.wantfail {
|
||||
t.Error("expected: fail, got &Db{}")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const timeformat string = "2006-01-02T15:04:05.000Z"
|
||||
|
||||
var dbtests = []struct {
|
||||
name string
|
||||
dbfile string
|
||||
wantfail bool
|
||||
id string
|
||||
context string
|
||||
ts string
|
||||
filter string
|
||||
query string
|
||||
upload common.Upload
|
||||
form common.Form
|
||||
}{
|
||||
{
|
||||
"upload", "test.db", false, "1", "foo",
|
||||
"2023-03-10T11:45:00.000Z", "", "",
|
||||
common.Upload{
|
||||
Id: "1", Expire: "asap", File: "none", Context: "foo",
|
||||
Created: common.Timestamp{}, Type: common.TypeUpload},
|
||||
common.Form{},
|
||||
},
|
||||
{
|
||||
"form", "test.db", false, "2", "foo",
|
||||
"2023-03-10T11:45:00.000Z", "", "",
|
||||
common.Upload{},
|
||||
common.Form{
|
||||
Id: "1", Expire: "asap", Description: "none", Context: "foo",
|
||||
Created: common.Timestamp{}, Type: common.TypeForm},
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
We need to test the whole Db operation in one run, because it
|
||||
doesn't work well if using a global Db.
|
||||
*/
|
||||
func TestDboperation(t *testing.T) {
|
||||
for _, tt := range dbtests {
|
||||
c := &cfg.Config{DbFile: tt.dbfile}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// create new bbolt db
|
||||
db, err := NewDb(c)
|
||||
defer finalize(db)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Could not open new DB: " + err.Error())
|
||||
}
|
||||
|
||||
if tt.upload.Id != "" {
|
||||
// set ts
|
||||
ts, err := time.Parse(timeformat, tt.ts)
|
||||
if err != nil {
|
||||
t.Error("Could not parse time: " + err.Error())
|
||||
}
|
||||
|
||||
tt.upload.Created = common.Timestamp{Time: ts}
|
||||
|
||||
// create new upload db object
|
||||
err = db.Insert(tt.id, tt.upload)
|
||||
if err != nil {
|
||||
t.Error("Could not insert new upload object: " + err.Error())
|
||||
}
|
||||
|
||||
// fetch it
|
||||
response, err := db.Get(tt.context, tt.id, common.TypeUpload)
|
||||
if err != nil {
|
||||
t.Error("Could not fetch upload object: " + err.Error())
|
||||
}
|
||||
|
||||
// is it there?
|
||||
if len(response.Uploads) != 1 {
|
||||
t.Error("db.Get() did not return an upload obj")
|
||||
}
|
||||
|
||||
// compare times
|
||||
if !tt.upload.Created.Time.Equal(response.Uploads[0].Created.Time) {
|
||||
t.Errorf("Timestamps don't match!\ngot: %s\nexp: %s\n",
|
||||
response.Uploads[0].Created, tt.upload.Created)
|
||||
}
|
||||
|
||||
// equal them artificially, because otherwise td will
|
||||
// fail because of time.Time.wall+ext, or TZ is missing
|
||||
response.Uploads[0].Created = tt.upload.Created
|
||||
|
||||
// compare
|
||||
td.Cmp(t, response.Uploads[0], &tt.upload, tt.name)
|
||||
|
||||
// fetch list
|
||||
response, err = db.List(tt.context, tt.filter, tt.query, common.TypeUpload)
|
||||
if err != nil {
|
||||
t.Error("Could not fetch uploads list: " + err.Error())
|
||||
}
|
||||
|
||||
// is it there?
|
||||
if len(response.Uploads) != 1 {
|
||||
t.Error("db.List() did not return upload obj[s]")
|
||||
}
|
||||
|
||||
// delete
|
||||
err = db.Delete(tt.context, tt.id)
|
||||
if err != nil {
|
||||
t.Error("Could not delete upload obj: " + err.Error())
|
||||
}
|
||||
|
||||
// fetch again, shall return empty
|
||||
_, err = db.Get(tt.context, tt.id, common.TypeUpload)
|
||||
if err == nil {
|
||||
t.Error("Could fetch upload object again although we deleted it")
|
||||
}
|
||||
}
|
||||
|
||||
if tt.form.Id != "" {
|
||||
// set ts
|
||||
ts, err := time.Parse(timeformat, tt.ts)
|
||||
if err != nil {
|
||||
t.Error("Could not parse time: " + err.Error())
|
||||
}
|
||||
tt.form.Created = common.Timestamp{Time: ts}
|
||||
|
||||
// create new form db object
|
||||
err = db.Insert(tt.id, tt.form)
|
||||
if err != nil {
|
||||
t.Error("Could not insert new form object: " + err.Error())
|
||||
}
|
||||
|
||||
// fetch it
|
||||
response, err := db.Get(tt.context, tt.id, common.TypeForm)
|
||||
if err != nil {
|
||||
t.Error("Could not fetch form object: " + err.Error())
|
||||
}
|
||||
|
||||
// is it there?
|
||||
if len(response.Forms) != 1 {
|
||||
t.Error("db.Get() did not return an form obj")
|
||||
}
|
||||
|
||||
// compare times
|
||||
if !tt.form.Created.Time.Equal(response.Forms[0].Created.Time) {
|
||||
t.Errorf("Timestamps don't match!\ngot: %s\nexp: %s\n",
|
||||
response.Forms[0].Created, tt.form.Created)
|
||||
}
|
||||
|
||||
// equal them artificially, because otherwise td will
|
||||
// fail because of time.Time.wall+ext, or TZ is missing
|
||||
response.Forms[0].Created = tt.form.Created
|
||||
|
||||
// compare
|
||||
td.Cmp(t, response.Forms[0], &tt.form, tt.name)
|
||||
|
||||
// fetch list
|
||||
response, err = db.List(tt.context, tt.filter, tt.query, common.TypeForm)
|
||||
if err != nil {
|
||||
t.Error("Could not fetch forms list: " + err.Error())
|
||||
}
|
||||
|
||||
// is it there?
|
||||
if len(response.Forms) != 1 {
|
||||
t.Error("db.FormsList() did not return form obj[s]")
|
||||
}
|
||||
|
||||
// delete
|
||||
err = db.Delete(tt.context, tt.id)
|
||||
if err != nil {
|
||||
t.Error("Could not delete form obj: " + err.Error())
|
||||
}
|
||||
|
||||
// fetch again, shall return empty
|
||||
_, err = db.Get(tt.context, tt.id, common.TypeForm)
|
||||
if err == nil {
|
||||
t.Error("Could fetch form object again although we deleted it")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
196
api/fileio.go
196
api/fileio.go
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// cleanup an upload directory, either because we got an error in the
|
||||
// middle of an upload or something else went wrong.
|
||||
func cleanup(dir string) {
|
||||
err := os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
Log("Failed to remove dir %s: %s", dir, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Extract form file[s] and store them on disk, returns a list of files
|
||||
func SaveFormFiles(c *fiber.Ctx, cfg *cfg.Config, files []*multipart.FileHeader, id string) ([]string, error) {
|
||||
members := []string{}
|
||||
for _, file := range files {
|
||||
filename, _ := common.Untaint(filepath.Base(file.Filename), cfg.RegNormalizedFilename)
|
||||
path := filepath.Join(cfg.StorageDir, id, filename)
|
||||
members = append(members, filename)
|
||||
Log("Received: %s => %s/%s", file.Filename, id, filename)
|
||||
|
||||
if err := c.SaveFile(file, path); err != nil {
|
||||
cleanup(filepath.Join(cfg.StorageDir, id))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return members, nil
|
||||
}
|
||||
|
||||
// generate return url. in case of multiple files, zip and remove them
|
||||
func ProcessFormFiles(cfg *cfg.Config, members []string, id string) (string, string, error) {
|
||||
returnUrl := ""
|
||||
Filename := ""
|
||||
|
||||
if len(members) == 1 {
|
||||
returnUrl = strings.Join([]string{cfg.Url, "download", id, members[0]}, "/")
|
||||
Filename = members[0]
|
||||
} else {
|
||||
zipfile := Ts() + "data.zip"
|
||||
tmpzip := filepath.Join(cfg.StorageDir, zipfile)
|
||||
finalzip := filepath.Join(cfg.StorageDir, id, zipfile)
|
||||
iddir := filepath.Join(cfg.StorageDir, id)
|
||||
|
||||
if err := ZipDir(iddir, tmpzip); err != nil {
|
||||
cleanup(iddir)
|
||||
Log("zip error")
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpzip, finalzip); err != nil {
|
||||
cleanup(iddir)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
returnUrl = strings.Join([]string{cfg.Url, "download", id, zipfile}, "/")
|
||||
Filename = zipfile
|
||||
|
||||
// clean up after us
|
||||
go func() {
|
||||
for _, file := range members {
|
||||
if err := os.Remove(filepath.Join(cfg.StorageDir, id, file)); err != nil {
|
||||
Log("ERROR: unable to delete %s: %s", file, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return returnUrl, Filename, nil
|
||||
}
|
||||
|
||||
// Create a zip archive from a directory
|
||||
// FIXME: -e option, if any, goes here
|
||||
func ZipDir(directory, zipfilename string) error {
|
||||
f, err := os.Create(zipfilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
writer := zip.NewWriter(f)
|
||||
defer writer.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
failure := make(chan string)
|
||||
|
||||
// don't chdir the server itself
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if err := os.Chdir(directory); err != nil {
|
||||
failure <- "Failed to changedir: " + err.Error()
|
||||
return
|
||||
}
|
||||
newDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
failure <- "Failed to get cwd: " + err.Error()
|
||||
}
|
||||
|
||||
if newDir != directory {
|
||||
failure <- "Failed to changedir!"
|
||||
}
|
||||
|
||||
err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||
// 2. Go through all the files of the directory
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Create a local file header
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set compression
|
||||
header.Method = zip.Deflate
|
||||
|
||||
// 4. Set relative path of a file as the header name
|
||||
header.Name = path
|
||||
//Log("a: <%s>, b: <%s>, rel: <%s>", filepath.Dir(directory), path, header.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
header.Name += "/"
|
||||
}
|
||||
|
||||
// 5. Create writer for the file header and save content of the file
|
||||
headerWriter, err := writer.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(headerWriter, f)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
failure <- "Failed to zip directory: " + err.Error()
|
||||
} else {
|
||||
close(failure)
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
goterr := <-failure
|
||||
|
||||
if goterr != "" {
|
||||
return errors.New(goterr)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
//"github.com/alecthomas/repr"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
|
||||
"bytes"
|
||||
"html/template"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
Validate a fied by untainting it, modifies field value inplace.
|
||||
*/
|
||||
func untaintField(c *fiber.Ctx, orig *string, r *regexp.Regexp, caption string) error {
|
||||
if len(*orig) != 0 {
|
||||
nt, err := common.Untaint(*orig, r)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid "+caption+": "+err.Error())
|
||||
}
|
||||
*orig = nt
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
id := uuid.NewString()
|
||||
|
||||
var formdata common.Form
|
||||
|
||||
// init form obj
|
||||
entry := &common.Form{Id: id, Created: common.Timestamp{Time: time.Now()}, Type: common.TypeForm}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
entry.Context = apicontext
|
||||
|
||||
// extract auxiliary form data (expire field et al)
|
||||
if err := c.BodyParser(&formdata); err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"bodyparser error : "+err.Error())
|
||||
}
|
||||
|
||||
// post process inputdata
|
||||
if len(formdata.Expire) == 0 {
|
||||
entry.Expire = "asap"
|
||||
} else {
|
||||
if err := untaintField(c, &formdata.Expire, cfg.RegDuration, "expire data"); err != nil {
|
||||
return err
|
||||
}
|
||||
entry.Expire = formdata.Expire
|
||||
}
|
||||
|
||||
if err := untaintField(c, &formdata.Notify, cfg.RegDuration, "email address"); err != nil {
|
||||
return err
|
||||
}
|
||||
entry.Notify = formdata.Notify
|
||||
|
||||
if err := untaintField(c, &formdata.Description, cfg.RegDuration, "description"); err != nil {
|
||||
return err
|
||||
}
|
||||
entry.Description = formdata.Description
|
||||
|
||||
// get url [and zip if there are multiple files]
|
||||
returnUrl := strings.Join([]string{cfg.Url, "form", id}, "/")
|
||||
entry.Url = returnUrl
|
||||
|
||||
Log("Now serving %s", returnUrl)
|
||||
Log("Expire set to: %s", entry.Expire)
|
||||
Log("Form created with API-Context %s", entry.Context)
|
||||
|
||||
// we do this in the background to not thwart the server
|
||||
go func() {
|
||||
if err := db.Insert(id, entry); err != nil {
|
||||
Log("Failed to insert: " + err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// everything went well so far
|
||||
res := &common.Response{Forms: []*common.Form{entry}}
|
||||
res.Success = true
|
||||
res.Code = fiber.StatusOK
|
||||
return c.Status(fiber.StatusOK).JSON(res)
|
||||
}
|
||||
|
||||
// delete form
|
||||
func FormDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid id provided!")
|
||||
}
|
||||
|
||||
if len(id) == 0 {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"No id specified!")
|
||||
}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
err = db.Delete(apicontext, id)
|
||||
if err != nil {
|
||||
// non existent db entry with that id, or other db error, see logs
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"No form with that id could be found!")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns the whole list + error code, no post processing by server
|
||||
func FormsList(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
// fetch filter from body(json expected)
|
||||
setcontext := new(SetContext)
|
||||
if err := c.BodyParser(setcontext); err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Unable to parse body: "+err.Error())
|
||||
}
|
||||
|
||||
filter, err := common.Untaint(setcontext.Apicontext, cfg.RegKey)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid api context filter provided!")
|
||||
}
|
||||
|
||||
query, err := common.Untaint(setcontext.Query, cfg.RegQuery)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid query provided!")
|
||||
}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
// get list
|
||||
response, err := db.List(apicontext, filter, query, common.TypeForm)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Unable to list forms: "+err.Error())
|
||||
}
|
||||
|
||||
// if we reached this point we can signal success
|
||||
response.Success = true
|
||||
response.Code = fiber.StatusOK
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
||||
|
||||
// returns just one form obj + error code
|
||||
func FormDescribe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid id provided!")
|
||||
}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
response, err := db.Get(apicontext, id, common.TypeForm)
|
||||
if err != nil || len(response.Forms) == 0 {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"No form with that id could be found!")
|
||||
}
|
||||
|
||||
for _, form := range response.Forms {
|
||||
form.Url = strings.Join([]string{cfg.Url, "form", id}, "/")
|
||||
}
|
||||
|
||||
// if we reached this point we can signal success
|
||||
response.Success = true
|
||||
response.Code = fiber.StatusOK
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
||||
|
||||
/*
|
||||
Render the upload html form. Template given by --formpage, stored
|
||||
as text in cfg.Formpage. It will be rendered using golang's
|
||||
template engine, data to be filled in is the form matching the
|
||||
given id.
|
||||
*/
|
||||
func FormPage(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallexpire bool) error {
|
||||
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).SendString("Invalid id provided!")
|
||||
}
|
||||
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).
|
||||
SendString("Unable to initialize session store from context:" + err.Error())
|
||||
}
|
||||
|
||||
response, err := db.Get(apicontext, id, common.TypeForm)
|
||||
if err != nil || len(response.Forms) == 0 {
|
||||
return c.Status(fiber.StatusForbidden).
|
||||
SendString("No form with that id could be found!")
|
||||
}
|
||||
|
||||
t := template.New("form")
|
||||
if t, err = t.Parse(cfg.Formpage); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).
|
||||
SendString("Unable to load form template: " + err.Error())
|
||||
}
|
||||
|
||||
// prepare upload url
|
||||
uploadurl := strings.Join([]string{cfg.ApiPrefix + ApiVersion, "uploads"}, "/")
|
||||
response.Forms[0].Url = uploadurl
|
||||
|
||||
var out bytes.Buffer
|
||||
if err := t.Execute(&out, response.Forms[0]); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).
|
||||
SendString("Unable to render form template: " + err.Error())
|
||||
}
|
||||
|
||||
c.Set("Content-type", "text/html; charset=utf-8")
|
||||
return c.Status(fiber.StatusOK).SendString(out.String())
|
||||
}
|
||||
|
||||
func FormModify(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
var formdata common.Form
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid id provided!")
|
||||
}
|
||||
|
||||
// extract form data
|
||||
if err := c.BodyParser(&formdata); err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"bodyparser error : "+err.Error())
|
||||
}
|
||||
|
||||
// post process input data
|
||||
if err := untaintField(c, &formdata.Expire, cfg.RegDuration, "expire data"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := untaintField(c, &formdata.Notify, cfg.RegDuration, "email address"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := untaintField(c, &formdata.Description, cfg.RegDuration, "description"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// lookup orig entry
|
||||
response, err := db.Get(apicontext, id, common.TypeForm)
|
||||
if err != nil || len(response.Forms) == 0 {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"No form with that id could be found!")
|
||||
}
|
||||
|
||||
form := response.Forms[0]
|
||||
|
||||
// modify fields
|
||||
if formdata.Expire != "" {
|
||||
form.Expire = formdata.Expire
|
||||
}
|
||||
|
||||
if formdata.Notify != "" {
|
||||
form.Notify = formdata.Notify
|
||||
}
|
||||
|
||||
if formdata.Description != "" {
|
||||
form.Description = formdata.Description
|
||||
}
|
||||
|
||||
// run in foreground because we need the feedback here
|
||||
if err := db.Insert(id, form); err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Failed to insert: "+err.Error())
|
||||
}
|
||||
|
||||
res := &common.Response{Forms: []*common.Form{form}}
|
||||
res.Success = true
|
||||
res.Code = fiber.StatusOK
|
||||
return c.Status(fiber.StatusOK).JSON(res)
|
||||
}
|
||||
53
api/mail.go
53
api/mail.go
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
var mailtpl string = `To: %s\r
|
||||
From: %s\r
|
||||
Subject: %s\r
|
||||
\r
|
||||
%s\r
|
||||
`
|
||||
|
||||
/*
|
||||
Send an email via an external mail gateway. SMTP Auth is
|
||||
required. Errors may occur with a time delay, like server timeouts
|
||||
etc. So only call it detached via go routine.
|
||||
*/
|
||||
func Sendmail(c *cfg.Config, recipient string, body string, subject string) error {
|
||||
// Message.
|
||||
message := []byte(fmt.Sprintf(mailtpl, recipient, c.Mail.From, subject, body))
|
||||
|
||||
// Authentication.
|
||||
auth := smtp.PlainAuth("", c.Mail.From, c.Mail.Password, c.Mail.Server)
|
||||
|
||||
// Sending email.
|
||||
Log("Trying to send mail to %s via %s:%s with subject %s",
|
||||
recipient, c.Mail.Server, c.Mail.Port, subject)
|
||||
err := smtp.SendMail(c.Mail.Server+":"+c.Mail.Port, auth, c.Mail.From, []string{recipient}, []byte(message))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
244
api/server.go
244
api/server.go
@@ -1,244 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/compress"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/requestid"
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
"github.com/gofiber/keyauth/v2"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
)
|
||||
|
||||
// sessions are context specific and can be global savely
|
||||
var Sessionstore *session.Store
|
||||
|
||||
const shallExpire = true
|
||||
|
||||
func Runserver(conf *cfg.Config, args []string) error {
|
||||
// required for authenticated routes, used to store the api context
|
||||
Sessionstore = session.New()
|
||||
|
||||
// bbolt db setup
|
||||
db, err := NewDb(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// setup authenticated endpoints
|
||||
auth := SetupAuthStore(conf, db)
|
||||
|
||||
// setup api server
|
||||
router := SetupServer(conf)
|
||||
|
||||
// authenticated routes
|
||||
api := router.Group(conf.ApiPrefix + ApiVersion)
|
||||
{
|
||||
// upload
|
||||
api.Post("/uploads", auth, func(c *fiber.Ctx) error {
|
||||
return UploadPost(c, conf, db)
|
||||
})
|
||||
|
||||
// remove
|
||||
api.Delete("/uploads/:id", auth, func(c *fiber.Ctx) error {
|
||||
err := UploadDelete(c, conf, db)
|
||||
return SendResponse(c, "", err)
|
||||
})
|
||||
|
||||
// listing
|
||||
api.Get("/uploads", auth, func(c *fiber.Ctx) error {
|
||||
return UploadsList(c, conf, db)
|
||||
})
|
||||
|
||||
// info/describe
|
||||
api.Get("/uploads/:id", auth, func(c *fiber.Ctx) error {
|
||||
return UploadDescribe(c, conf, db)
|
||||
})
|
||||
|
||||
// modify
|
||||
api.Put("/uploads/:id", auth, func(c *fiber.Ctx) error {
|
||||
return UploadModify(c, conf, db)
|
||||
})
|
||||
|
||||
// download w/o expire
|
||||
api.Get("/uploads/:id/file", auth, func(c *fiber.Ctx) error {
|
||||
return UploadFetch(c, conf, db)
|
||||
})
|
||||
|
||||
// same for forms ************
|
||||
api.Post("/forms", auth, func(c *fiber.Ctx) error {
|
||||
return FormCreate(c, conf, db)
|
||||
})
|
||||
|
||||
// remove
|
||||
api.Delete("/forms/:id", auth, func(c *fiber.Ctx) error {
|
||||
err := FormDelete(c, conf, db)
|
||||
return SendResponse(c, "", err)
|
||||
})
|
||||
|
||||
// listing
|
||||
api.Get("/forms", auth, func(c *fiber.Ctx) error {
|
||||
return FormsList(c, conf, db)
|
||||
})
|
||||
|
||||
// info/describe
|
||||
api.Get("/forms/:id", auth, func(c *fiber.Ctx) error {
|
||||
return FormDescribe(c, conf, db)
|
||||
})
|
||||
|
||||
// modify
|
||||
api.Put("/forms/:id", auth, func(c *fiber.Ctx) error {
|
||||
return FormModify(c, conf, db)
|
||||
})
|
||||
}
|
||||
|
||||
// public routes
|
||||
{
|
||||
router.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.Send([]byte(conf.Frontpage))
|
||||
})
|
||||
|
||||
router.Get("/download/:id/:file", func(c *fiber.Ctx) error {
|
||||
return UploadFetch(c, conf, db, shallExpire)
|
||||
})
|
||||
|
||||
router.Get("/download/:id", func(c *fiber.Ctx) error {
|
||||
return UploadFetch(c, conf, db, shallExpire)
|
||||
})
|
||||
|
||||
router.Get("/form/:id", func(c *fiber.Ctx) error {
|
||||
return FormPage(c, conf, db, shallExpire)
|
||||
})
|
||||
|
||||
router.Get("/status", func(c *fiber.Ctx) error {
|
||||
return Status(c, conf)
|
||||
})
|
||||
}
|
||||
|
||||
// setup cleaner
|
||||
quitcleaner := BackgroundCleaner(conf, db)
|
||||
|
||||
router.Hooks().OnShutdown(func() error {
|
||||
Log("Shutting down cleaner")
|
||||
close(quitcleaner)
|
||||
return nil
|
||||
})
|
||||
|
||||
return router.Listen(conf.Listen)
|
||||
}
|
||||
|
||||
func SetupAuthStore(conf *cfg.Config, db *Db) func(*fiber.Ctx) error {
|
||||
AuthSetApikeys(conf.Apicontexts)
|
||||
|
||||
return keyauth.New(keyauth.Config{
|
||||
Validator: func(c *fiber.Ctx, key string) (bool, error) {
|
||||
// we use a wrapper closure to be able to forward the db object
|
||||
formuser, err := AuthValidateOnetimeKey(c, key, db)
|
||||
|
||||
// incoming apicontext matches a form id, accept it
|
||||
if err == nil {
|
||||
Log("Incoming API Context equals formuser: %t, id: %s", formuser, key)
|
||||
return formuser, err
|
||||
}
|
||||
|
||||
// nope, we need to check against regular configured apicontexts
|
||||
return AuthValidateAPIKey(c, key)
|
||||
},
|
||||
ErrorHandler: AuthErrHandler,
|
||||
})
|
||||
}
|
||||
|
||||
func SetupServer(conf *cfg.Config) *fiber.App {
|
||||
router := fiber.New(fiber.Config{
|
||||
CaseSensitive: true,
|
||||
StrictRouting: true,
|
||||
Immutable: true,
|
||||
Prefork: conf.Prefork,
|
||||
ServerHeader: "ephemerup Server",
|
||||
AppName: conf.AppName,
|
||||
BodyLimit: conf.BodyLimit,
|
||||
Network: conf.Network,
|
||||
})
|
||||
|
||||
router.Use(requestid.New())
|
||||
|
||||
router.Use(logger.New(logger.Config{
|
||||
Format: "${pid} ${locals:requestid} ${status} - ${method} ${path}\n",
|
||||
}))
|
||||
|
||||
router.Use(cors.New(cors.Config{
|
||||
AllowMethods: "GET,PUT,POST,DELETE",
|
||||
ExposeHeaders: "Content-Type,Authorization,Accept",
|
||||
}))
|
||||
|
||||
router.Use(compress.New(compress.Config{
|
||||
Level: compress.LevelBestSpeed,
|
||||
}))
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
/*
|
||||
Wrapper to respond with proper json status, message and code,
|
||||
shall be prepared and called by the handlers directly.
|
||||
*/
|
||||
func JsonStatus(c *fiber.Ctx, code int, msg string) error {
|
||||
success := true
|
||||
|
||||
if code != fiber.StatusOK {
|
||||
success = false
|
||||
}
|
||||
|
||||
return c.Status(code).JSON(common.Result{
|
||||
Code: code,
|
||||
Message: msg,
|
||||
Success: success,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
Used for non json-aware handlers, called by server
|
||||
*/
|
||||
func SendResponse(c *fiber.Ctx, msg string, err error) error {
|
||||
if err != nil {
|
||||
code := fiber.StatusInternalServerError
|
||||
|
||||
var e *fiber.Error
|
||||
if errors.As(err, &e) {
|
||||
code = e.Code
|
||||
}
|
||||
|
||||
return c.Status(code).JSON(common.Result{
|
||||
Code: code,
|
||||
Message: err.Error(),
|
||||
Success: false,
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(common.Result{
|
||||
Code: fiber.StatusOK,
|
||||
Message: msg,
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
)
|
||||
|
||||
func Status(c *fiber.Ctx, cfg *cfg.Config) error {
|
||||
res := &common.Response{}
|
||||
res.Success = true
|
||||
res.Code = fiber.StatusOK
|
||||
res.Message = "up and running"
|
||||
return c.Status(fiber.StatusOK).JSON(res)
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
//"github.com/alecthomas/repr"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SetContext struct {
|
||||
Apicontext string `json:"apicontext" form:"apicontext"`
|
||||
Query string `json:"query" form:"query"`
|
||||
}
|
||||
|
||||
func UploadPost(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
// supports upload of multiple files with:
|
||||
//
|
||||
// curl -X POST localhost:8080/putfile \
|
||||
// -F "upload[]=@/home/scip/2023-02-06_10-51.png" \
|
||||
// -F "upload[]=@/home/scip/pgstat.png" \
|
||||
// -H "Content-Type: multipart/form-data"
|
||||
//
|
||||
// If multiple files are uploaded, they are zipped, otherwise
|
||||
// the file is being stored as is.
|
||||
//
|
||||
// Returns the name of the uploaded file.
|
||||
|
||||
id := uuid.NewString()
|
||||
|
||||
var returnUrl string
|
||||
var formdata Meta
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(cfg.StorageDir, id), os.ModePerm); err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize directories: "+err.Error())
|
||||
}
|
||||
|
||||
// fetch auxiliary form data
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"mime/multipart error "+err.Error())
|
||||
}
|
||||
|
||||
// init upload obj
|
||||
entry := &common.Upload{Id: id, Created: common.Timestamp{Time: time.Now()}, Type: common.TypeUpload}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
entry.Context = apicontext
|
||||
|
||||
// retrieve files, if any
|
||||
files := form.File["upload[]"]
|
||||
members, err := SaveFormFiles(c, cfg, files, id)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Could not store uploaded file[s]: "+err.Error())
|
||||
}
|
||||
entry.Members = members
|
||||
|
||||
// extract auxiliary form data (expire field et al)
|
||||
if err := c.BodyParser(&formdata); err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"bodyparser error : "+err.Error())
|
||||
}
|
||||
|
||||
// post process expire
|
||||
if len(formdata.Expire) == 0 {
|
||||
entry.Expire = "asap"
|
||||
} else {
|
||||
ex, err := common.Untaint(formdata.Expire, cfg.RegDuration) // duration or asap allowed
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid data: "+err.Error())
|
||||
}
|
||||
entry.Expire = ex
|
||||
}
|
||||
|
||||
// get url [and zip if there are multiple files]
|
||||
returnUrl, Newfilename, err := ProcessFormFiles(cfg, entry.Members, id)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Could not process uploaded file[s]: "+err.Error())
|
||||
}
|
||||
entry.File = Newfilename
|
||||
entry.Url = returnUrl
|
||||
|
||||
Log("Now serving %s from %s/%s", returnUrl, cfg.StorageDir, id)
|
||||
Log("Expire set to: %s", entry.Expire)
|
||||
Log("Uploaded with API-Context %s", entry.Context)
|
||||
|
||||
// we do this in the background to not thwart the server
|
||||
go func() {
|
||||
if err := db.Insert(id, entry); err != nil {
|
||||
Log("Failed to insert: " + err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// everything went well so far
|
||||
res := &common.Response{Uploads: []*common.Upload{entry}}
|
||||
res.Success = true
|
||||
res.Code = fiber.StatusOK
|
||||
|
||||
// ok, check if we need to remove a form, if so we do it in the
|
||||
// background. delete error doesn't lead to upload failure, we
|
||||
// only log it. same applies to mail notification.
|
||||
formid, _ := SessionGetFormId(c)
|
||||
if formid != "" {
|
||||
go func() {
|
||||
r, err := db.Get(apicontext, formid, common.TypeForm)
|
||||
if err == nil {
|
||||
if len(r.Forms) == 1 {
|
||||
if r.Forms[0].Expire == "asap" {
|
||||
if err := db.Delete(apicontext, formid); err != nil {
|
||||
Log("Failed to delete formid %s: %s", formid, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// email notification to form creator
|
||||
if r.Forms[0].Notify != "" {
|
||||
body := fmt.Sprintf("Upload is available under: %s", returnUrl)
|
||||
subject := fmt.Sprintf("Upload form %s has been used", formid)
|
||||
err := Sendmail(cfg, r.Forms[0].Notify, body, subject)
|
||||
if err != nil {
|
||||
Log("Failed to send mail: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(res)
|
||||
}
|
||||
|
||||
func UploadFetch(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) error {
|
||||
// deliver a file and delete it if expire is set to asap
|
||||
|
||||
// we ignore c.Params("file"), cause it may be malign. Also we've
|
||||
// got it in the db anyway
|
||||
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
|
||||
if err != nil {
|
||||
return fiber.NewError(403, "Invalid id provided!")
|
||||
}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
response, err := db.Lookup(apicontext, id, common.TypeUpload)
|
||||
if err != nil {
|
||||
// non existent db entry with that id, or other db error, see logs
|
||||
return fiber.NewError(404, "No download with that id could be found!")
|
||||
}
|
||||
|
||||
var upload *common.Upload
|
||||
if len(response.Uploads) > 0 {
|
||||
upload = response.Uploads[0]
|
||||
}
|
||||
|
||||
file := upload.File
|
||||
filename := filepath.Join(cfg.StorageDir, id, file)
|
||||
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
// db entry is there, but file isn't (anymore?)
|
||||
go func() {
|
||||
if err := db.Delete(apicontext, id); err != nil {
|
||||
Log("Unable to delete entry id %s: %s", id, err.Error())
|
||||
}
|
||||
}()
|
||||
return fiber.NewError(404, "No download with that id could be found!")
|
||||
}
|
||||
|
||||
// finally put the file to the client
|
||||
err = c.Download(filename, file)
|
||||
|
||||
if len(shallExpire) > 0 {
|
||||
if shallExpire[0] {
|
||||
go func() {
|
||||
// check if we need to delete the file now and do it in the background
|
||||
if upload.Expire == "asap" {
|
||||
cleanup(filepath.Join(cfg.StorageDir, id))
|
||||
if err := db.Delete(apicontext, id); err != nil {
|
||||
Log("Unable to delete entry id %s: %s", id, err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// delete file, id dir and db entry
|
||||
func UploadDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
|
||||
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid id provided!")
|
||||
}
|
||||
|
||||
if len(id) == 0 {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"No id specified!")
|
||||
}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
err = db.Delete(apicontext, id)
|
||||
if err != nil {
|
||||
// non existent db entry with that id, or other db error, see logs
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"No upload with that id could be found!")
|
||||
}
|
||||
|
||||
cleanup(filepath.Join(cfg.StorageDir, id))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns the whole list + error code, no post processing by server
|
||||
func UploadsList(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
// fetch apifilter+query from body(json expected)
|
||||
setcontext := new(SetContext)
|
||||
if err := c.BodyParser(setcontext); err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Unable to parse body: "+err.Error())
|
||||
}
|
||||
|
||||
apifilter, err := common.Untaint(setcontext.Apicontext, cfg.RegKey)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid api context apifilter provided!")
|
||||
}
|
||||
|
||||
query, err := common.Untaint(setcontext.Query, cfg.RegQuery)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid query provided!")
|
||||
}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
// get list
|
||||
uploads, err := db.List(apicontext, apifilter, query, common.TypeUpload)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Unable to list uploads: "+err.Error())
|
||||
}
|
||||
|
||||
// if we reached this point we can signal success
|
||||
uploads.Success = true
|
||||
uploads.Code = fiber.StatusOK
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(uploads)
|
||||
}
|
||||
|
||||
// returns just one upload obj + error code, no post processing by server
|
||||
func UploadDescribe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid id provided!")
|
||||
}
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
response, err := db.Get(apicontext, id, common.TypeUpload)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"No upload with that id could be found!")
|
||||
}
|
||||
|
||||
for _, upload := range response.Uploads {
|
||||
upload.Url = strings.Join([]string{cfg.Url, "download", id, upload.File}, "/")
|
||||
}
|
||||
|
||||
// if we reached this point we can signal success
|
||||
response.Success = true
|
||||
response.Code = fiber.StatusOK
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
||||
|
||||
func UploadModify(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
var formdata common.Upload
|
||||
|
||||
// retrieve the API Context name from the session
|
||||
apicontext, err := SessionGetApicontext(c)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"Unable to initialize session store from context: "+err.Error())
|
||||
}
|
||||
|
||||
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
|
||||
if err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Invalid id provided!")
|
||||
}
|
||||
|
||||
// extract form data
|
||||
if err := c.BodyParser(&formdata); err != nil {
|
||||
return JsonStatus(c, fiber.StatusInternalServerError,
|
||||
"bodyparser error : "+err.Error())
|
||||
}
|
||||
|
||||
// post process input data
|
||||
if err := untaintField(c, &formdata.Expire, cfg.RegDuration, "expire data"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := untaintField(c, &formdata.Description, cfg.RegDuration, "description"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// lookup orig entry
|
||||
response, err := db.Get(apicontext, id, common.TypeUpload)
|
||||
if err != nil || len(response.Uploads) == 0 {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"No upload with that id could be found!")
|
||||
}
|
||||
|
||||
upload := response.Uploads[0]
|
||||
|
||||
// modify fields
|
||||
if formdata.Expire != "" {
|
||||
upload.Expire = formdata.Expire
|
||||
}
|
||||
|
||||
if formdata.Description != "" {
|
||||
upload.Description = formdata.Description
|
||||
}
|
||||
|
||||
// run in foreground because we need the feedback here
|
||||
if err := db.Insert(id, upload); err != nil {
|
||||
return JsonStatus(c, fiber.StatusForbidden,
|
||||
"Failed to insert: "+err.Error())
|
||||
}
|
||||
|
||||
res := &common.Response{Uploads: []*common.Upload{upload}}
|
||||
res.Success = true
|
||||
res.Code = fiber.StatusOK
|
||||
return c.Status(fiber.StatusOK).JSON(res)
|
||||
}
|
||||
116
api/utils.go
116
api/utils.go
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
const ApiVersion string = "/v1"
|
||||
|
||||
// Binding from JSON, data coming from user, not tainted
|
||||
type Meta struct {
|
||||
Expire string `json:"expire" form:"expire"`
|
||||
}
|
||||
|
||||
// incoming id
|
||||
type Id struct {
|
||||
Id string `json:"name" xml:"name" form:"name"`
|
||||
}
|
||||
|
||||
// vaious helbers
|
||||
func Log(format string, values ...any) {
|
||||
fmt.Printf("[DEBUG] "+format+"\n", values...)
|
||||
}
|
||||
|
||||
func Ts() string {
|
||||
t := time.Now()
|
||||
return t.Format("2006-01-02-15-04-")
|
||||
}
|
||||
|
||||
/*
|
||||
Retrieve the API Context name from the session, assuming is has
|
||||
been successfully authenticated. However, if there are no api
|
||||
contexts defined, we'll use 'default' (set in
|
||||
auth.validateAPIKey()).
|
||||
|
||||
If there's no apicontext in the session, assume unauth user, return ""
|
||||
*/
|
||||
func SessionGetApicontext(c *fiber.Ctx) (string, error) {
|
||||
sess, err := Sessionstore.Get(c)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to initialize session store from context: %s", err.Error())
|
||||
}
|
||||
|
||||
apicontext := sess.Get("apicontext")
|
||||
if apicontext != nil {
|
||||
return apicontext.(string), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
Retrieve the formid (aka onetime api key) from the session. It is
|
||||
configured if an upload request has been successfully authenticated
|
||||
using a onetime key.
|
||||
*/
|
||||
func SessionGetFormId(c *fiber.Ctx) (string, error) {
|
||||
sess, err := Sessionstore.Get(c)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to initialize session store from context: %s", err.Error())
|
||||
}
|
||||
|
||||
formid := sess.Get("formid")
|
||||
if formid != nil {
|
||||
return formid.(string), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate if time is up based on start time.Time and
|
||||
duration. Returns true if time is expired. Start time comes from
|
||||
the database.
|
||||
|
||||
aka:
|
||||
|
||||
if(now - start) >= duration { time is up}
|
||||
*/
|
||||
func IsExpired(conf *cfg.Config, start time.Time, duration string) bool {
|
||||
var expiretime int // seconds
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if duration == "asap" {
|
||||
expiretime = conf.DefaultExpire
|
||||
} else {
|
||||
expiretime = common.Duration2int(duration)
|
||||
}
|
||||
|
||||
if now.Unix()-start.Unix() >= int64(expiretime) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
130
cfg/config.go
130
cfg/config.go
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const Version string = "v0.0.3"
|
||||
|
||||
var VERSION string // maintained by -x
|
||||
|
||||
type Apicontext struct {
|
||||
Context string `koanf:"context"` // aka name or tenant
|
||||
Key string `koanf:"key"`
|
||||
}
|
||||
|
||||
type Mailsettings struct {
|
||||
Server string `koanf:"server"`
|
||||
Port string `koanf:"port"`
|
||||
From string `koanf:"from"`
|
||||
Password string `koanf:"password"`
|
||||
}
|
||||
|
||||
// holds the whole configs, filled by commandline flags, env and config file
|
||||
type Config struct {
|
||||
// Flags+config file settings
|
||||
ApiPrefix string `koanf:"apiprefix"` // path prefix
|
||||
Debug bool `koanf:"debug"`
|
||||
Listen string `koanf:"listen"` // [host]:port
|
||||
StorageDir string `koanf:"storagedir"` // db and uploads go there
|
||||
Url string `koanf:"url"` // public visible url, might be different from Listen
|
||||
DbFile string `koanf:"dbfile"`
|
||||
Super string `koanf:"super"` // the apicontext which has all permissions
|
||||
Frontpage string `koanf:"frontpage"` // a html file
|
||||
Formpage string `koanf:"formpage"` // a html file
|
||||
|
||||
// fiber settings, see:
|
||||
// https://docs.gofiber.io/api/fiber/#config
|
||||
Prefork bool `koanf:"prefork"` // default: nope
|
||||
AppName string `koanf:"appname"` // "upd"
|
||||
BodyLimit int `koanf:"bodylimit"` // much
|
||||
V4only bool `koanf:"ipv4"`
|
||||
V6only bool `koanf:"ipv6"`
|
||||
Network string
|
||||
|
||||
// only settable via config
|
||||
Apicontexts []Apicontext `koanf:"apicontexts"`
|
||||
|
||||
// smtp settings
|
||||
Mail Mailsettings `koanf:"mail"`
|
||||
|
||||
// Internals only
|
||||
RegNormalizedFilename *regexp.Regexp
|
||||
RegDuration *regexp.Regexp
|
||||
RegKey *regexp.Regexp
|
||||
RegEmail *regexp.Regexp
|
||||
RegText *regexp.Regexp
|
||||
RegQuery *regexp.Regexp
|
||||
|
||||
CleanInterval time.Duration
|
||||
DefaultExpire int
|
||||
}
|
||||
|
||||
func Getversion() string {
|
||||
// main program version
|
||||
|
||||
// generated version string, used by -v contains cfg.Version on
|
||||
// main branch, and cfg.Version-$branch-$lastcommit-$date on
|
||||
// development branch
|
||||
|
||||
return fmt.Sprintf("This is ephemerup server version %s", VERSION)
|
||||
}
|
||||
|
||||
func (c *Config) GetVersion() string {
|
||||
return VERSION
|
||||
}
|
||||
|
||||
// post processing of options, if any
|
||||
func (c *Config) ApplyDefaults() {
|
||||
if len(c.Url) == 0 {
|
||||
if strings.HasPrefix(c.Listen, ":") {
|
||||
c.Url = "http://localhost" + c.Listen
|
||||
} else {
|
||||
c.Url = "http://" + c.Listen
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.V4only && c.V6only:
|
||||
c.Network = "tcp" // dual stack
|
||||
case c.V4only:
|
||||
c.Network = "tcp4"
|
||||
case c.V6only:
|
||||
c.Network = "tcp6"
|
||||
default:
|
||||
if c.Prefork {
|
||||
c.Network = "tcp4"
|
||||
} else {
|
||||
c.Network = "tcp" // dual stack
|
||||
}
|
||||
}
|
||||
|
||||
c.RegNormalizedFilename = regexp.MustCompile(`[^\w\d\-_\.]`)
|
||||
c.RegDuration = regexp.MustCompile(`[^dhms0-9]`)
|
||||
c.RegKey = regexp.MustCompile(`[^a-zA-Z0-9\-]`)
|
||||
c.RegEmail = regexp.MustCompile(`[^a-zA-Z0-9._%+\-@0-9]`)
|
||||
c.RegText = regexp.MustCompile(`[^a-zA-Z0-9_%+\-@0-9 #/\.]`)
|
||||
c.RegQuery = regexp.MustCompile(`[^a-zA-Z0-9_%+\-@0-9 #/\.\*\[\]\(\)\\]`)
|
||||
|
||||
c.CleanInterval = 10 * time.Second
|
||||
c.DefaultExpire = 30 * 86400 // 1 month
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: eph-ephemerup-config
|
||||
namespace: "eph"
|
||||
labels:
|
||||
app.kubernetes.io/name: ephemerup
|
||||
helm.sh/chart: ephemerup-1.0.0
|
||||
app.kubernetes.io/instance: eph
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
annotations:
|
||||
app: ephemerup
|
||||
data:
|
||||
listen = "8080"
|
||||
bodylimit = "1024"
|
||||
super = "root"
|
||||
mail = {
|
||||
server =
|
||||
port =
|
||||
from =
|
||||
password =
|
||||
}
|
||||
apicontexts = [
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
@@ -1,21 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: ephemerup
|
||||
description: |
|
||||
A Helm chart for Ephemerup.
|
||||
|
||||
type: application
|
||||
|
||||
sources:
|
||||
- https://codeberg.org/scip/ephemerup
|
||||
|
||||
version: 1.0.0
|
||||
|
||||
appVersion: "0.0.2"
|
||||
|
||||
dependencies:
|
||||
- name: common
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
tags:
|
||||
- bitnami-common
|
||||
version: 1.x.x
|
||||
# icon: ""
|
||||
@@ -1,126 +0,0 @@
|
||||
# ephemerup
|
||||
|
||||
  
|
||||
|
||||
A Helm chart for Ephemerup.
|
||||
|
||||
## Source Code
|
||||
|
||||
* <https://codeberg.org/scip/ephemerup>
|
||||
|
||||
## Requirements
|
||||
|
||||
| Repository | Name | Version |
|
||||
|------------|------|---------|
|
||||
| https://charts.bitnami.com/bitnami | common | 1.x.x |
|
||||
|
||||
## Values
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| affinity | object | `{}` | |
|
||||
| clusterDomain | string | `"cluster.local"` | |
|
||||
| commonAnnotations.app | string | `"ephemerup"` | |
|
||||
| commonLabels | object | `{}` | |
|
||||
| config.apicontexts[0].context | string | `"root"` | |
|
||||
| config.apicontexts[0].key | string | `"0fddbff5d8010f81cd28a7d77f3e38981b13d6164c2fd6e1c3f60a4287630c37"` | |
|
||||
| config.bodylimit | int | `1024` | |
|
||||
| config.listen | int | `8080` | |
|
||||
| config.mail.from | string | `"root@localhost"` | |
|
||||
| config.mail.port | int | `25` | |
|
||||
| config.mail.server | string | `"localhost"` | |
|
||||
| config.super | string | `"root"` | |
|
||||
| containerSecurityContext.allowPrivilegeEscalation | bool | `false` | |
|
||||
| containerSecurityContext.capabilities.drop[0] | string | `"ALL"` | |
|
||||
| containerSecurityContext.enabled | bool | `false` | |
|
||||
| containerSecurityContext.privileged | bool | `false` | |
|
||||
| containerSecurityContext.runAsNonRoot | bool | `false` | |
|
||||
| containerSecurityContext.runAsUser | int | `0` | |
|
||||
| customLivenessProbe | object | `{}` | |
|
||||
| customReadinessProbe | object | `{}` | |
|
||||
| customStartupProbe | object | `{}` | |
|
||||
| env | list | `[]` | |
|
||||
| fullnameOverride | string | `""` | |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||
| image.pullSecrets | list | `[]` | |
|
||||
| image.registry | string | `"ghcr.io/tlinden"` | |
|
||||
| image.repository | string | `"ephemerup"` | |
|
||||
| image.tag | string | `"latest"` | |
|
||||
| ingress.annotations | object | `{}` | |
|
||||
| ingress.apiVersion | string | `""` | |
|
||||
| ingress.enabled | bool | `false` | |
|
||||
| ingress.extraHosts | list | `[]` | |
|
||||
| ingress.extraPaths | list | `[]` | |
|
||||
| ingress.extraRules | list | `[]` | |
|
||||
| ingress.extraTls | list | `[]` | |
|
||||
| ingress.hostname | string | `"ephemerup.local"` | |
|
||||
| ingress.ingressClassName | string | `"nginx"` | |
|
||||
| ingress.path | string | `"/"` | |
|
||||
| ingress.pathType | string | `"Prefix"` | |
|
||||
| ingress.secrets | list | `[]` | |
|
||||
| ingress.selfSigned | bool | `false` | |
|
||||
| ingress.tls | bool | `false` | |
|
||||
| ingress.tlsSecretName | string | `""` | |
|
||||
| kubeVersion | string | `""` | |
|
||||
| lifecycleHooks | object | `{}` | |
|
||||
| livenessProbe.enabled | bool | `true` | |
|
||||
| livenessProbe.failureThreshold | int | `6` | |
|
||||
| livenessProbe.initialDelaySeconds | int | `5` | |
|
||||
| livenessProbe.periodSeconds | int | `20` | |
|
||||
| livenessProbe.successThreshold | int | `1` | |
|
||||
| livenessProbe.timeoutSeconds | int | `1` | |
|
||||
| logLevel | string | `"info"` | |
|
||||
| metrics.serviceMonitor.enabled | bool | `false` | |
|
||||
| metrics.serviceMonitor.interval | string | `"30s"` | |
|
||||
| metrics.serviceMonitor.namespace | string | `""` | |
|
||||
| metrics.serviceMonitor.port | string | `"http"` | |
|
||||
| metrics.serviceMonitor.scrapeTimeout | string | `"10s"` | |
|
||||
| mountSecrets | list | `[]` | |
|
||||
| nameOverride | string | `""` | |
|
||||
| namespaceOverride | string | `""` | |
|
||||
| nodeAffinityPreset.key | string | `""` | |
|
||||
| nodeAffinityPreset.type | string | `""` | |
|
||||
| nodeAffinityPreset.values | list | `[]` | |
|
||||
| nodeSelector | object | `{}` | |
|
||||
| podAffinityPreset | string | `""` | |
|
||||
| podAnnotations | object | `{}` | |
|
||||
| podAntiAffinityPreset | string | `"soft"` | |
|
||||
| podLabels | object | `{}` | |
|
||||
| podSecurityContext.fsGroup | int | `65534` | |
|
||||
| readinessProbe.enabled | bool | `true` | |
|
||||
| readinessProbe.failureThreshold | int | `6` | |
|
||||
| readinessProbe.initialDelaySeconds | int | `5` | |
|
||||
| readinessProbe.periodSeconds | int | `20` | |
|
||||
| readinessProbe.successThreshold | int | `1` | |
|
||||
| readinessProbe.timeoutSeconds | int | `1` | |
|
||||
| replicaCount | int | `1` | |
|
||||
| resources.limits.cpu | string | `"500m"` | |
|
||||
| resources.limits.memory | string | `"256Mi"` | |
|
||||
| resources.requests.cpu | string | `"100m"` | |
|
||||
| resources.requests.memory | string | `"128Mi"` | |
|
||||
| secrets | object | `{}` | |
|
||||
| service.annotations | object | `{}` | |
|
||||
| service.clusterIP | string | `""` | |
|
||||
| service.externalTrafficPolicy | string | `"Cluster"` | |
|
||||
| service.extraPorts | list | `[]` | |
|
||||
| service.loadBalancerIP | string | `""` | |
|
||||
| service.loadBalancerSourceRanges | list | `[]` | |
|
||||
| service.nodePorts.http | string | `""` | |
|
||||
| service.ports.http | int | `8080` | |
|
||||
| service.sessionAffinity | string | `"None"` | |
|
||||
| service.sessionAffinityConfig | object | `{}` | |
|
||||
| service.type | string | `"ClusterIP"` | |
|
||||
| sidecars | list | `[]` | |
|
||||
| startupProbe.enabled | bool | `true` | |
|
||||
| startupProbe.failureThreshold | int | `6` | |
|
||||
| startupProbe.initialDelaySeconds | int | `10` | |
|
||||
| startupProbe.periodSeconds | int | `20` | |
|
||||
| startupProbe.successThreshold | int | `1` | |
|
||||
| startupProbe.timeoutSeconds | int | `1` | |
|
||||
| storage.longTerm | object | `{"name":"ephemerup-storage","spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"standard"}}` | Persistent volume for bolt database and uploads |
|
||||
| storage.tmp | object | `{"name":"ephemerup-tmp","spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"standard"}}` | Persistent volume for temporary files |
|
||||
| tolerations | list | `[]` | |
|
||||
| updateStrategy.type | string | `"RollingUpdate"` | |
|
||||
|
||||
----------------------------------------------
|
||||
Autogenerated from chart metadata using [helm-docs v1.11.2](https://github.com/norwoodj/helm-docs/releases/v1.11.2)
|
||||
Binary file not shown.
@@ -1,51 +0,0 @@
|
||||
CHART NAME: {{ .Chart.Name }}
|
||||
CHART VERSION: {{ .Chart.Version }}
|
||||
APP VERSION: {{ .Chart.AppVersion }}
|
||||
|
||||
** Please be patient while the chart is being deployed **
|
||||
|
||||
Application can be accessed through the following DNS name from within your cluster:
|
||||
|
||||
{{ include "common.names.fullname" . }}.{{ include "common.names.namespace" . }}.svc.{{ .Values.clusterDomain }} (port {{ .Values.service.ports.http }})
|
||||
|
||||
To access Application from outside the cluster execute the following commands:
|
||||
|
||||
{{- if .Values.ingress.enabled }}
|
||||
|
||||
1. Get the Application URL and associate its hostname to your cluster external IP:
|
||||
|
||||
export CLUSTER_IP=$(minikube ip) # On Minikube. Use: `kubectl cluster-info` on others K8s clusters
|
||||
echo "Application URL: http{{ if .Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.hostname }}"
|
||||
echo "$CLUSTER_IP {{ .Values.ingress.hostname }}" | sudo tee -a /etc/hosts
|
||||
|
||||
{{- else }}
|
||||
|
||||
1. Get the Application URL by running these commands:
|
||||
|
||||
{{- if contains "NodePort" .Values.service.type }}
|
||||
|
||||
export NODE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "common.names.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo "http://${NODE_IP}:${NODE_PORT}"
|
||||
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ include "common.names.namespace" . }} svc -w {{ include "common.names.fullname" . }}'
|
||||
|
||||
export SERVICE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[0].port}" services {{ include "common.names.fullname" . }})
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ include "common.names.namespace" . }} {{ include "common.names.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||
echo "http://${SERVICE_IP}:${SERVICE_PORT}"
|
||||
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
|
||||
export SERVICE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[0].port}" services {{ include "common.names.fullname" . }})
|
||||
kubectl port-forward --namespace {{ include "common.names.namespace" . }} svc/{{ include "common.names.fullname" . }} ${SERVICE_PORT}:${SERVICE_PORT} &
|
||||
echo "http://127.0.0.1:${SERVICE_PORT}"
|
||||
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
2. Access Application using the obtained URL.
|
||||
|
||||
{{- include "common.warnings.rollingTag" .Values.image }}
|
||||
@@ -1,8 +0,0 @@
|
||||
{{/*
|
||||
Return the proper image name
|
||||
*/}}
|
||||
{{- define "ephemerup.image" -}}
|
||||
{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
{{- if (.Values.config) }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ template "common.names.fullname" . }}-config
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "common.labels.standard" . | nindent 4 }}
|
||||
{{- if .Values.commonLabels }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
ephemerup.hcl: |-
|
||||
listen = {{ .Values.config.listen | quote }}
|
||||
bodylimit = {{ .Values.config.bodylimit | quote }}
|
||||
{{- if .Values.config.url }}
|
||||
url = {{ .Values.config.url | quote }}
|
||||
{{- end }}
|
||||
super = {{ .Values.config.super | quote }}
|
||||
mail = {
|
||||
server = {{ .Values.config.mail.server | quote }}
|
||||
port = {{ .Values.config.mail.port | quote }}
|
||||
from = {{ .Values.config.mail.from | quote }}
|
||||
{{- if .Values.config.password }}
|
||||
password = {{ .Values.config.password | quote }}
|
||||
{{- end }}
|
||||
}
|
||||
apicontexts = [
|
||||
{{- range $context := .Values.config.apicontexts }}
|
||||
{
|
||||
context = {{ $context.context | quote }}
|
||||
key = {{ $context.key | quote }}
|
||||
}
|
||||
{{- end }}
|
||||
]
|
||||
storagedir = "/data"
|
||||
{{- end }}
|
||||
@@ -1,63 +0,0 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
apiVersion: {{ include "common.capabilities.ingress.apiVersion" . }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "common.names.fullname" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "common.labels.standard" . | nindent 4 }}
|
||||
{{- if .Values.commonLabels }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
{{- if .Values.ingress.certManager }}
|
||||
kubernetes.io/tls-acme: "true"
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.annotations }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.ingress.annotations "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.ingressClassName (include "common.ingress.supportsIngressClassname" .) }}
|
||||
ingressClassName: {{ .Values.ingress.ingressClassName | quote }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- if .Values.ingress.hostname }}
|
||||
- host: {{ .Values.ingress.hostname }}
|
||||
http:
|
||||
paths:
|
||||
{{- if .Values.ingress.extraPaths }}
|
||||
{{- toYaml .Values.ingress.extraPaths | nindent 10 }}
|
||||
{{- end }}
|
||||
- path: {{ .Values.ingress.path }}
|
||||
{{- if eq "true" (include "common.ingress.supportsPathType" .) }}
|
||||
pathType: {{ .Values.ingress.pathType }}
|
||||
{{- end }}
|
||||
backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" .) "servicePort" "http" "context" $) | nindent 14 }}
|
||||
{{- end }}
|
||||
{{- range .Values.ingress.extraHosts }}
|
||||
- host: {{ .name }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ default "/" .path }}
|
||||
{{- if eq "true" (include "common.ingress.supportsPathType" $) }}
|
||||
pathType: {{ default "ImplementationSpecific" .pathType }}
|
||||
{{- end }}
|
||||
backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" $) "servicePort" "http" "context" $) | nindent 14 }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.extraRules }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.ingress.extraRules "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if or .Values.ingress.tls .Values.ingress.extraTls .Values.ingress.hosts }}
|
||||
tls:
|
||||
{{- if .Values.ingress.tls }}
|
||||
- hosts:
|
||||
- {{ .Values.ingress.hostname }}
|
||||
secretName: {{ default (printf "%s-tls" .Values.ingress.hostname) .Values.ingress.tlsSecretName }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.extraTls }}
|
||||
{{- toYaml .Values.ingress.extraTls | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,18 +0,0 @@
|
||||
{{- if .Values.secrets }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ template "common.names.fullname" . }}-config
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "common.labels.standard" . | nindent 4 }}
|
||||
{{- if .Values.commonLabels }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
{{- with .Values.secrets }}
|
||||
{{ toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{ end }}
|
||||
@@ -1,33 +0,0 @@
|
||||
|
||||
{{- if .Values.metrics.serviceMonitor.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: {{ include "common.names.fullname" . }}-sm
|
||||
{{- if .Values.metrics.serviceMonitor.namespace }}
|
||||
namespace: {{ .Values.metrics.serviceMonitor.namespace }}
|
||||
{{- end }}
|
||||
labels: {{- include "common.labels.standard" . | nindent 4 }}
|
||||
{{- if .Values.commonLabels }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "common.labels.standard" . | nindent 6 }}
|
||||
endpoints:
|
||||
- path: /metrics
|
||||
port: {{ .Values.metrics.serviceMonitor.port }}
|
||||
{{- if .Values.metrics.serviceMonitor.interval }}
|
||||
interval: {{ .Values.metrics.serviceMonitor.interval }}
|
||||
{{- end }}
|
||||
{{- if .Values.metrics.serviceMonitor.scrapeTimeout }}
|
||||
scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }}
|
||||
{{- end }}
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
@@ -1,49 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "common.names.fullname" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "common.labels.standard" . | nindent 4 }}
|
||||
{{- if .Values.commonLabels }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
{{- if .Values.service.annotations }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.service.annotations "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
{{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }}
|
||||
clusterIP: {{ .Values.service.clusterIP }}
|
||||
{{- end }}
|
||||
{{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP)) }}
|
||||
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
|
||||
{{- end }}
|
||||
{{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerSourceRanges)) }}
|
||||
loadBalancerSourceRanges: {{ .Values.service.loadBalancerSourceRanges }}
|
||||
{{- end }}
|
||||
{{- if or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") }}
|
||||
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.service.sessionAffinity }}
|
||||
sessionAffinity: {{ .Values.service.sessionAffinity }}
|
||||
{{- end }}
|
||||
{{- if .Values.service.sessionAffinityConfig }}
|
||||
sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.service.sessionAffinityConfig "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.service.ports.http }}
|
||||
targetPort: http
|
||||
{{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.http))) }}
|
||||
nodePort: {{ .Values.service.nodePorts.http }}
|
||||
{{- else if eq .Values.service.type "ClusterIP" }}
|
||||
nodePort: null
|
||||
{{- end }}
|
||||
{{- if .Values.service.extraPorts }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.service.extraPorts "context" $) | nindent 4 }}
|
||||
{{- end }}
|
||||
selector: {{- include "common.labels.matchLabels" . | nindent 4 }}
|
||||
@@ -1,127 +0,0 @@
|
||||
{{- $fullName := include "common.names.fullname" . -}}
|
||||
apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }}
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "common.names.fullname" . }}
|
||||
namespace: {{ include "common.names.namespace" . | quote }}
|
||||
labels: {{- include "common.labels.standard" . | nindent 4 }}
|
||||
{{- if .Values.commonLabels }}
|
||||
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.commonAnnotations }}
|
||||
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
serviceName: {{ include "common.names.fullname" . }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/configMap: {{ toYaml .Values.config | sha256sum }}
|
||||
{{- if .Values.podAnnotations }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
labels: {{- include "common.labels.standard" . | nindent 8 }}
|
||||
{{- if .Values.podLabels }}
|
||||
{{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
containers:
|
||||
- name: ephemerup
|
||||
image: {{ include "ephemerup.image" . }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy | quote}}
|
||||
command: ['/app/ephemerupd', '-c', '/config/ephemerup.hcl']
|
||||
env:
|
||||
{{- range $envVar := .Values.env }}
|
||||
- name: {{ $envVar.name }}
|
||||
value: {{ $envVar.value }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.ports.http }}
|
||||
protocol: TCP
|
||||
{{- if .Values.livenessProbe.enabled }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: {{ .Values.service.ports.http }}
|
||||
path: /status
|
||||
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
|
||||
successThreshold: {{ .Values.livenessProbe.successThreshold }}
|
||||
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
|
||||
{{- else if .Values.customLivenessProbe }}
|
||||
livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.readinessProbe.enabled }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: {{ .Values.service.ports.http }}
|
||||
path: /status
|
||||
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
|
||||
successThreshold: {{ .Values.readinessProbe.successThreshold }}
|
||||
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
|
||||
{{- else if .Values.customReadinessProbe }}
|
||||
readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.startupProbe.enabled }}
|
||||
startupProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.service.ports.http }}
|
||||
initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.startupProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }}
|
||||
successThreshold: {{ .Values.startupProbe.successThreshold }}
|
||||
failureThreshold: {{ .Values.startupProbe.failureThreshold }}
|
||||
{{- else if .Values.customStartupProbe }}
|
||||
startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.resources }}
|
||||
resources: {{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.containerSecurityContext.enabled }}
|
||||
securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: "ephemerup-storage"
|
||||
mountPath: "/data"
|
||||
- name: "ephemerup-tmp"
|
||||
mountPath: "/tmp"
|
||||
- mountPath: "/config/ephemerup.hcl"
|
||||
name: config
|
||||
subPath: "ephemerup.hcl"
|
||||
{{- range $secret := .Values.mountSecrets }}
|
||||
- mountPath: "/secret/{{ $secret.name }}"
|
||||
name: {{ $secret.name }}
|
||||
{{- end }}
|
||||
|
||||
securityContext:
|
||||
{{ toYaml .Values.podSecurityContext | nindent 8 | trim }}
|
||||
{{- if .Values.nodeSelector }}
|
||||
nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.nodeSelector "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: {{ template "common.names.fullname" . }}-config
|
||||
items:
|
||||
- key: ephemerup.hcl
|
||||
path: ephemerup.hcl
|
||||
{{- range $secret := .Values.mountSecrets }}
|
||||
- name: {{ $secret.name }}
|
||||
secret:
|
||||
secretName: {{ $secret.name }}
|
||||
{{- end }}
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: {{ .Values.storage.longTerm.name }}
|
||||
spec:
|
||||
{{ toYaml .Values.storage.longTerm.spec | nindent 6 | trim }}
|
||||
- metadata:
|
||||
name: {{ .Values.storage.tmp.name }}
|
||||
spec:
|
||||
{{ toYaml .Values.storage.tmp.spec | nindent 6 | trim }}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft-07/schema#",
|
||||
"title": "Values schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"replicaCount": {
|
||||
"type": "integer",
|
||||
"enum": [0, 1]
|
||||
}
|
||||
},
|
||||
"required": ["replicaCount"]
|
||||
}
|
||||
@@ -1,472 +0,0 @@
|
||||
## @section Common parameters
|
||||
##
|
||||
|
||||
## @param kubeVersion Override Kubernetes version
|
||||
##
|
||||
kubeVersion: ""
|
||||
## @param nameOverride String to partially override aspnet-core.fullname
|
||||
##
|
||||
nameOverride: ""
|
||||
## @param fullnameOverride String to fully override aspnet-core.fullname
|
||||
##
|
||||
fullnameOverride: ""
|
||||
## @param namespaceOverride String to fully override common.names.namespace
|
||||
##
|
||||
namespaceOverride: ""
|
||||
## @param commonLabels Labels to add to all deployed objects
|
||||
##
|
||||
commonLabels: {}
|
||||
## @param commonAnnotations Annotations to add to all deployed objects
|
||||
##
|
||||
commonAnnotations:
|
||||
app: ephemerup
|
||||
|
||||
## @param clusterDomain Kubernetes cluster domain name
|
||||
##
|
||||
clusterDomain: cluster.local
|
||||
|
||||
logLevel: info
|
||||
|
||||
##
|
||||
image:
|
||||
registry: ghcr.io/tlinden
|
||||
repository: ephemerup
|
||||
tag: "latest"
|
||||
## Specify a imagePullPolicy
|
||||
## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
|
||||
## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images
|
||||
##
|
||||
pullPolicy: IfNotPresent
|
||||
## Optionally specify an array of imagePullSecrets.
|
||||
## Secrets must be manually created in the namespace.
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
|
||||
## e.g:
|
||||
## pullSecrets:
|
||||
## - myRegistryKeySecretName
|
||||
##
|
||||
pullSecrets: []
|
||||
|
||||
secrets: {}
|
||||
|
||||
mountSecrets: []
|
||||
# - name: my-secret
|
||||
|
||||
# Environment variables
|
||||
env: []
|
||||
# - name: HTTPS_PROXY
|
||||
# value: "http://localhost:3128"
|
||||
|
||||
# Ephemerup configuration
|
||||
config:
|
||||
## must be the same as in the service spec below
|
||||
listen: 8080
|
||||
## max bytes allowed to upload
|
||||
bodylimit: 1024
|
||||
## optional public visible url
|
||||
#url:
|
||||
## root context which has all permissions
|
||||
super: "root"
|
||||
## mail config
|
||||
mail:
|
||||
server: "localhost"
|
||||
port: 25
|
||||
from: "root@localhost"
|
||||
## required when using SMTP Auth
|
||||
#password: ""
|
||||
## context config, add more as needed
|
||||
apicontexts:
|
||||
- context: "root"
|
||||
key: "0fddbff5d8010f81cd28a7d77f3e38981b13d6164c2fd6e1c3f60a4287630c37"
|
||||
|
||||
|
||||
## @param replicaCount Number of application replicas to deploy
|
||||
##
|
||||
replicaCount: 1
|
||||
|
||||
## @param sidecars Add additional sidecar containers to the application pods
|
||||
## e.g:
|
||||
## sidecars:
|
||||
## - name: your-image-name
|
||||
## image: your-image
|
||||
## imagePullPolicy: Always
|
||||
## ports:
|
||||
## - name: portname
|
||||
## containerPort: 1234
|
||||
##
|
||||
sidecars: []
|
||||
|
||||
## @param lifecycleHooks Add lifecycle hooks to the application deployment
|
||||
##
|
||||
lifecycleHooks: {}
|
||||
|
||||
## @param podAnnotations Annotations for application pods
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
##
|
||||
podAnnotations: {}
|
||||
|
||||
## @param podLabels Extra labels for application pods
|
||||
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
##
|
||||
podLabels: {}
|
||||
|
||||
## @param updateStrategy.type Deployment strategy type
|
||||
## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies
|
||||
##
|
||||
updateStrategy:
|
||||
## StrategyType
|
||||
## Can be set to RollingUpdate or OnDelete
|
||||
##
|
||||
type: RollingUpdate
|
||||
|
||||
## @param podAffinityPreset Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
|
||||
## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
|
||||
##
|
||||
podAffinityPreset: ""
|
||||
|
||||
## @param podAntiAffinityPreset Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
|
||||
## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
|
||||
##
|
||||
podAntiAffinityPreset: soft
|
||||
|
||||
## Node affinity preset
|
||||
## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
|
||||
##
|
||||
nodeAffinityPreset:
|
||||
## @param nodeAffinityPreset.type Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
|
||||
##
|
||||
type: ""
|
||||
## @param nodeAffinityPreset.key Node label key to match. Ignored if `affinity` is set
|
||||
##
|
||||
key: ""
|
||||
## @param nodeAffinityPreset.values Node label values to match. Ignored if `affinity` is set
|
||||
## E.g.
|
||||
## values:
|
||||
## - e2e-az1
|
||||
## - e2e-az2
|
||||
##
|
||||
values: []
|
||||
|
||||
## @param affinity Affinity for pod assignment
|
||||
## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
|
||||
## NOTE: podAffinityPreset, podAntiAffinityPreset, and nodeAffinityPreset will be ignored when it's set
|
||||
##
|
||||
affinity: {}
|
||||
## @param nodeSelector Node labels for pod assignment
|
||||
## ref: https://kubernetes.io/docs/user-guide/node-selection/
|
||||
##
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
## @param tolerations Tolerations for pod assignment
|
||||
## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
|
||||
##
|
||||
tolerations: []
|
||||
|
||||
## application containers' resource requests and limits
|
||||
## ref: https://kubernetes.io/docs/user-guide/compute-resources/
|
||||
|
||||
## @param resources.limits The resources limits for the application container
|
||||
## @param resources.requests The requested resources for the application container
|
||||
##
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
## Configure Pods Security Context
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod
|
||||
## @param podSecurityContext.fsGroup Set Security Context fsGroup
|
||||
podSecurityContext:
|
||||
fsGroup: 65534
|
||||
|
||||
## Configure Container Security Context (only main container)
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||
## @param containerSecurityContext.enabled Enabled application containers' Security Context
|
||||
## @param containerSecurityContext.runAsUser Set application container's Security Context runAsUser
|
||||
## @param containerSecurityContext.runAsNonRoot Set container's Security Context runAsNonRoot
|
||||
##
|
||||
containerSecurityContext:
|
||||
enabled: false
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
privileged: false
|
||||
runAsUser: 0
|
||||
runAsNonRoot: false
|
||||
|
||||
## Configure extra options for application containers' liveness and readiness probes
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes
|
||||
## @param livenessProbe.enabled Enable livenessProbe
|
||||
## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe
|
||||
## @param livenessProbe.periodSeconds Period seconds for livenessProbe
|
||||
## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe
|
||||
## @param livenessProbe.failureThreshold Failure threshold for livenessProbe
|
||||
## @param livenessProbe.successThreshold Success threshold for livenessProbe
|
||||
##
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 5
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 20
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
|
||||
## @param readinessProbe.enabled Enable readinessProbe
|
||||
## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe
|
||||
## @param readinessProbe.periodSeconds Period seconds for readinessProbe
|
||||
## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe
|
||||
## @param readinessProbe.failureThreshold Failure threshold for readinessProbe
|
||||
## @param readinessProbe.successThreshold Success threshold for readinessProbe
|
||||
##
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 5
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 20
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
|
||||
## Configure extra options for application containers' startup and readiness probes
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-startup-readiness-probes/#configure-probes
|
||||
## @param startupProbe.enabled Enable startupProbe
|
||||
## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe
|
||||
## @param startupProbe.periodSeconds Period seconds for startupProbe
|
||||
## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe
|
||||
## @param startupProbe.failureThreshold Failure threshold for startupProbe
|
||||
## @param startupProbe.successThreshold Success threshold for startupProbe
|
||||
##
|
||||
startupProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 10
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 20
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
|
||||
## @param customLivenessProbe Custom livenessProbe that overrides the default one
|
||||
##
|
||||
customLivenessProbe: {}
|
||||
|
||||
## @param customStartupProbe Custom startupProbe that overrides the default one
|
||||
##
|
||||
customStartupProbe: {}
|
||||
|
||||
## @param customReadinessProbe Custom readinessProbe that overrides the default one
|
||||
##
|
||||
customReadinessProbe: {}
|
||||
|
||||
## @section Traffic Exposure Parameters
|
||||
##
|
||||
|
||||
## application Service parameters.
|
||||
##
|
||||
service:
|
||||
## @param service.type application service type
|
||||
##
|
||||
type: ClusterIP
|
||||
## @param service.ports.http application service HTTP port
|
||||
##
|
||||
ports:
|
||||
http: 8080
|
||||
## @param service.nodePorts.http Node ports to expose
|
||||
## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
|
||||
##
|
||||
nodePorts:
|
||||
http: ""
|
||||
## @param service.clusterIP application service Cluster IP
|
||||
## e.g.:
|
||||
## clusterIP: None
|
||||
##
|
||||
clusterIP: ""
|
||||
## @param service.extraPorts Extra ports to expose (normally used with the `sidecar` value)
|
||||
##
|
||||
extraPorts: []
|
||||
## @param service.loadBalancerIP application service Load Balancer IP
|
||||
## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-loadbalancer
|
||||
##
|
||||
loadBalancerIP: ""
|
||||
## @param service.loadBalancerSourceRanges application service Load Balancer sources
|
||||
## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service
|
||||
## e.g:
|
||||
## loadBalancerSourceRanges:
|
||||
## - 10.10.10.0/24
|
||||
##
|
||||
loadBalancerSourceRanges: []
|
||||
## @param service.externalTrafficPolicy application service external traffic policy
|
||||
## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip
|
||||
##
|
||||
externalTrafficPolicy: Cluster
|
||||
## @param service.annotations Additional custom annotations for application service
|
||||
##
|
||||
annotations: {}
|
||||
## @param service.sessionAffinity Session Affinity for Kubernetes service, can be "None" or "ClientIP"
|
||||
## If "ClientIP", consecutive client requests will be directed to the same Pod
|
||||
## ref: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
|
||||
##
|
||||
sessionAffinity: None
|
||||
## @param service.sessionAffinityConfig Additional settings for the sessionAffinity
|
||||
## sessionAffinityConfig:
|
||||
## clientIP:
|
||||
## timeoutSeconds: 300
|
||||
##
|
||||
sessionAffinityConfig: {}
|
||||
|
||||
## Configure the ingress resource that allows you to access the application app
|
||||
## ref: https://kubernetes.io/docs/user-guide/ingress/
|
||||
##
|
||||
ingress:
|
||||
## @param ingress.enabled Enable ingress record generation for application
|
||||
##
|
||||
enabled: false
|
||||
## @param ingress.pathType Ingress path type
|
||||
##
|
||||
pathType: Prefix
|
||||
## @param ingress.apiVersion Force Ingress API version (automatically detected if not set)
|
||||
##
|
||||
apiVersion: ""
|
||||
## @param ingress.hostname Default host for the ingress resource, a host pointing to this will be created
|
||||
##
|
||||
hostname: ephemerup.local
|
||||
## @param ingress.path Default path for the ingress record
|
||||
##
|
||||
path: /
|
||||
## @param ingress.annotations Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations.
|
||||
## For a full list of possible ingress annotations, please see
|
||||
## ref: https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md
|
||||
## Use this parameter to set the required annotations for cert-manager, see
|
||||
## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations
|
||||
##
|
||||
## e.g:
|
||||
## annotations:
|
||||
## kubernetes.io/ingress.class: nginx
|
||||
## cert-manager.io/cluster-issuer: cluster-issuer-name
|
||||
##
|
||||
annotations: {}
|
||||
## @param ingress.tls Enable TLS configuration for the host defined at `ingress.hostname` parameter
|
||||
## TLS certificates will be retrieved from a TLS secret with name: `{{- printf "%s-tls" .Values.ingress.hostname }}`
|
||||
## You can:
|
||||
## - Use the `ingress.secrets` parameter to create this TLS secret
|
||||
## - Rely on cert-manager to create it by setting the corresponding annotations
|
||||
##
|
||||
tls: false
|
||||
tlsSecretName: ""
|
||||
## @param ingress.extraPaths Any additional arbitrary paths that may need to be added to the ingress under the main host.
|
||||
## For example: The ALB ingress controller requires a special rule for handling SSL redirection.
|
||||
## extraPaths:
|
||||
## - path: /*
|
||||
## backend:
|
||||
## serviceName: ssl-redirect
|
||||
## servicePort: use-annotation
|
||||
##
|
||||
extraPaths: []
|
||||
## @param ingress.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm
|
||||
##
|
||||
selfSigned: false
|
||||
## @param ingress.ingressClassName IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+)
|
||||
## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster .
|
||||
## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/
|
||||
##
|
||||
ingressClassName: "nginx"
|
||||
|
||||
## @param ingress.extraHosts An array with additional hostname(s) to be covered with the ingress record
|
||||
## e.g:
|
||||
## extraHosts:
|
||||
## - name: aspnet-core.local
|
||||
## path: /
|
||||
##
|
||||
extraHosts: []
|
||||
## @param ingress.extraTls TLS configuration for additional hostname(s) to be covered with this ingress record
|
||||
## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
|
||||
## e.g:
|
||||
## extraTls:
|
||||
## - hosts:
|
||||
## - aspnet-core.local
|
||||
## secretName: aspnet-core.local-tls
|
||||
##
|
||||
extraTls: []
|
||||
## @param ingress.secrets Custom TLS certificates as secrets
|
||||
## NOTE: 'key' and 'certificate' are expected in PEM format
|
||||
## NOTE: 'name' should line up with a 'secretName' set further up
|
||||
## If it is not set and you're using cert-manager, this is unneeded, as it will create a secret for you with valid certificates
|
||||
## If it is not set and you're NOT using cert-manager either, self-signed certificates will be created
|
||||
## It is also possible to create and manage the certificates outside of this helm chart
|
||||
## Please see README.md for more information
|
||||
## e.g:
|
||||
## secrets:
|
||||
## - name: aspnet-core.local-tls
|
||||
## key: |-
|
||||
## -----BEGIN RSA PRIVATE KEY-----
|
||||
## ...
|
||||
## -----END RSA PRIVATE KEY-----
|
||||
## certificate: |-
|
||||
## -----BEGIN CERTIFICATE-----
|
||||
## ...
|
||||
## -----END CERTIFICATE-----
|
||||
##
|
||||
secrets: []
|
||||
## @param ingress.extraRules Additional rules to be covered with this ingress record
|
||||
## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rules
|
||||
## e.g:
|
||||
## extraRules:
|
||||
## - host: aspnet-core.local
|
||||
## http:
|
||||
## path: /
|
||||
## backend:
|
||||
## service:
|
||||
## name: aspnet-core-svc
|
||||
## port:
|
||||
## name: http
|
||||
##
|
||||
extraRules: []
|
||||
|
||||
## @section RBAC parameters
|
||||
##
|
||||
|
||||
metrics:
|
||||
serviceMonitor:
|
||||
## @param metrics.serviceMonitor.enabled Creates a Prometheus Operator ServiceMonitor (also requires `metrics.enabled` to be `true`)
|
||||
##
|
||||
enabled: false
|
||||
|
||||
port: "http"
|
||||
## @param metrics.serviceMonitor.namespace Namespace in which Prometheus is running
|
||||
##
|
||||
namespace: ""
|
||||
## @param metrics.serviceMonitor.interval Interval at which metrics should be scraped.
|
||||
## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint
|
||||
## e.g:
|
||||
## interval: 10s
|
||||
##
|
||||
interval: "30s"
|
||||
## @param metrics.serviceMonitor.scrapeTimeout Timeout after which the scrape is ended
|
||||
## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint
|
||||
## e.g:
|
||||
## scrapeTimeout: 10s
|
||||
##
|
||||
scrapeTimeout: "10s"
|
||||
|
||||
storage:
|
||||
# -- Persistent volume for bolt database and uploads
|
||||
longTerm:
|
||||
name: "ephemerup-storage"
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
storageClassName: standard
|
||||
# -- Persistent volume for temporary files
|
||||
tmp:
|
||||
name: "ephemerup-tmp"
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
storageClassName: standard
|
||||
@@ -1,106 +0,0 @@
|
||||
package cmd
|
||||
|
||||
const formtemplate = `
|
||||
<!DOCTYPE html>
|
||||
<!-- -*-web-*- -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="description" content="upload form" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>File upload form</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet" integrity="sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h4>Upload form {{ .Id }}</h4>
|
||||
<!-- Response -->
|
||||
<div class="statusMsg"></div>
|
||||
|
||||
<!-- File upload form -->
|
||||
<div class="col-lg-12">
|
||||
<form id="UploadForm" enctype="multipart/form-data" action="/v1/uploads" method="POST">
|
||||
<div class="mb-3 row">
|
||||
<p>
|
||||
Use this form to upload one or more files. The creator of the form will automatically get notified.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-2 col-form-label">Description</label>
|
||||
<label class="col-sm-10 col-form-label">{{ .Description}} </label>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label for="file" class="col-sm-2 col-form-label">Select</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="file" class="form-control" id="file" name="uploads[]" multiple
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<label for="display" class="col-sm-2 col-form-label">Selected Files</label>
|
||||
<div class="col-sm-10">
|
||||
<!-- <input type="textara" class="form-control" id="upload-file-info" readonly>-->
|
||||
<div id="upload-file-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="submit" name="submit" class="btn btn-success submitBtn" value="Upload"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
// Submit form data via Ajax
|
||||
$("#UploadForm").on('submit', function(e){
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/v1/uploads',
|
||||
data: new FormData(this),
|
||||
dataType: 'json',
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function(xhr){
|
||||
$('.submitBtn').attr("disabled","disabled");
|
||||
$('#UploadForm').css("opacity",".5");
|
||||
xhr.setRequestHeader('Authorization', 'Bearer {{.Id}}');
|
||||
},
|
||||
success: function(response){
|
||||
$('.statusMsg').html('');
|
||||
if(response.success){
|
||||
$('#UploadForm')[0].reset();
|
||||
$('.statusMsg').html('<p class="alert alert-success">Your upload is available for download.<!-- '
|
||||
+response.uploads[0].url+' -->');
|
||||
$('#UploadForm').hide();
|
||||
}else{
|
||||
$('.statusMsg').html('<p class="alert alert-danger">'+response.message+'</p>');
|
||||
}
|
||||
$('#UploadForm').css("opacity","");
|
||||
$(".submitBtn").removeAttr("disabled");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#file").on('change', function() {
|
||||
$("#upload-file-info").empty();
|
||||
for (var i = 0; i < $(this).get(0).files.length; ++i) {
|
||||
$("#upload-file-info").append('<i class="bi-check-lg"></i> ' + $(this).get(0).files[i].name + '<br>');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
`
|
||||
209
cmd/root.go
209
cmd/root.go
@@ -1,209 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/knadh/koanf/parsers/hcl"
|
||||
"github.com/knadh/koanf/providers/env"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/providers/posflag"
|
||||
"github.com/knadh/koanf/v2"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
"github.com/alecthomas/repr"
|
||||
"codeberg.org/scip/ephemerup/api"
|
||||
"codeberg.org/scip/ephemerup/cfg"
|
||||
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
)
|
||||
|
||||
func Execute() error {
|
||||
var (
|
||||
conf cfg.Config
|
||||
ShowVersion bool
|
||||
)
|
||||
|
||||
f := flag.NewFlagSet("config", flag.ContinueOnError)
|
||||
f.Usage = func() {
|
||||
fmt.Println(f.FlagUsages())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
f.BoolVarP(&ShowVersion, "version", "v", false, "Print program version")
|
||||
f.StringVarP(&cfgFile, "config", "c", "", "custom config file")
|
||||
f.BoolVarP(&conf.Debug, "debug", "d", false, "Enable debugging")
|
||||
f.StringVarP(&conf.Listen, "listen", "l", ":8080", "listen to custom ip:port (use [ip]:port for ipv6)")
|
||||
f.StringVarP(&conf.StorageDir, "storagedir", "s", "/tmp", "storage directory for uploaded files")
|
||||
f.StringVarP(&conf.ApiPrefix, "apiprefix", "a", "", "API endpoint path")
|
||||
f.StringVarP(&conf.Url, "url", "u", "", "HTTP endpoint w/o path")
|
||||
f.StringVarP(&conf.DbFile, "dbfile", "D", "/tmp/uploads.db", "Bold database file to use")
|
||||
f.StringVarP(&conf.Super, "super", "", "", "The API Context which has permissions on all contexts")
|
||||
f.StringVarP(&conf.Frontpage, "frontpage", "", "welcome to upload api, use /api enpoint!",
|
||||
"Content or filename to be displayed on / in case someone visits")
|
||||
f.StringVarP(&conf.Formpage, "formpage", "", "", "Content or filename to be displayed for forms (must be a go template)")
|
||||
|
||||
// server settings
|
||||
f.BoolVarP(&conf.V4only, "ipv4", "4", false, "Only listen on ipv4")
|
||||
f.BoolVarP(&conf.V6only, "ipv6", "6", false, "Only listen on ipv6")
|
||||
|
||||
f.BoolVarP(&conf.Prefork, "prefork", "p", false, "Prefork server threads")
|
||||
f.StringVarP(&conf.AppName, "appname", "n", "ephemerupd "+conf.GetVersion(), "App name to say hi as")
|
||||
f.IntVarP(&conf.BodyLimit, "bodylimit", "b", 10250000000, "Max allowed upload size in bytes")
|
||||
|
||||
if err := f.Parse(os.Args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// exclude -6 and -4
|
||||
if conf.V4only && conf.V6only {
|
||||
return errors.New("You cannot mix -4 and -6!")
|
||||
}
|
||||
|
||||
// config provider
|
||||
var k = koanf.New(".")
|
||||
|
||||
// Load the config files provided in the commandline or the default locations
|
||||
var configfiles []string
|
||||
configfile, _ := f.GetString("config")
|
||||
if configfile != "" {
|
||||
configfiles = []string{configfile}
|
||||
} else {
|
||||
configfiles = []string{
|
||||
"/etc/ephemerup.hcl", "/usr/local/etc/ephemerup.hcl", // unix variants
|
||||
filepath.Join(os.Getenv("HOME"), ".config", "ephemerup", "ephemerup.hcl"),
|
||||
filepath.Join(os.Getenv("HOME"), ".ephemerup"),
|
||||
"ephemerup.hcl",
|
||||
}
|
||||
}
|
||||
repr.Print(configfiles)
|
||||
for _, cfgfile := range configfiles {
|
||||
if _, err := os.Stat(cfgfile); err == nil {
|
||||
if err := k.Load(file.Provider(cfgfile), hcl.Parser(true)); err != nil {
|
||||
return errors.New("error loading config file: " + err.Error())
|
||||
}
|
||||
}
|
||||
// else: we ignore the file if it doesn't exists
|
||||
}
|
||||
|
||||
// env overrides config file
|
||||
if err := k.Load(env.Provider("EPHEMERUPD_", ".", func(s string) string {
|
||||
return strings.Replace(strings.ToLower(
|
||||
strings.TrimPrefix(s, "EPHEMERUPD_")), "_", ".", -1)
|
||||
}), nil); err != nil {
|
||||
return errors.New("error loading environment: " + err.Error())
|
||||
}
|
||||
|
||||
// command line overrides env
|
||||
if err := k.Load(posflag.Provider(f, ".", k), nil); err != nil {
|
||||
return errors.New("error loading config: " + err.Error())
|
||||
}
|
||||
|
||||
// fetch values
|
||||
if err := k.Unmarshal("", &conf); err != nil {
|
||||
return errors.New("error unmarshalling: " + err.Error())
|
||||
}
|
||||
|
||||
// there may exist some api context variables
|
||||
GetApicontextsFromEnv(&conf)
|
||||
|
||||
if conf.Debug {
|
||||
repr.Print(conf)
|
||||
}
|
||||
|
||||
// Frontpage?
|
||||
if conf.Frontpage != "" {
|
||||
if _, err := os.Stat(conf.Frontpage); err == nil {
|
||||
// it's a filename, try to use it
|
||||
content, err := ioutil.ReadFile(conf.Frontpage)
|
||||
if err != nil {
|
||||
return errors.New("error loading config: " + err.Error())
|
||||
}
|
||||
|
||||
// replace the filename
|
||||
conf.Frontpage = string(content)
|
||||
}
|
||||
}
|
||||
|
||||
// Formpage?
|
||||
if conf.Formpage != "" {
|
||||
if _, err := os.Stat(conf.Formpage); err == nil {
|
||||
// it's a filename, try to use it
|
||||
content, err := ioutil.ReadFile(conf.Formpage)
|
||||
if err != nil {
|
||||
return errors.New("error loading config: " + err.Error())
|
||||
}
|
||||
|
||||
// replace the filename
|
||||
conf.Formpage = string(content)
|
||||
}
|
||||
} else {
|
||||
// use builtin default
|
||||
conf.Formpage = formtemplate
|
||||
}
|
||||
|
||||
switch {
|
||||
case ShowVersion:
|
||||
fmt.Println(cfg.Getversion())
|
||||
return nil
|
||||
default:
|
||||
conf.ApplyDefaults()
|
||||
return api.Runserver(&conf, flag.Args())
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Get a list of Api Contexts from ENV. Useful for use with k8s secrets.
|
||||
|
||||
Multiple env vars are supported in this format:
|
||||
|
||||
EPHEMERUPD_CONTEXT_$(NAME)="<context>:<key>"
|
||||
|
||||
eg:
|
||||
|
||||
EPHEMERUPD_CONTEXT_SUPPORT="support:tymag-fycyh-gymof-dysuf-doseb-puxyx"
|
||||
^^^^^^^- doesn't matter.
|
||||
|
||||
Modifies cfg.Config directly
|
||||
*/
|
||||
func GetApicontextsFromEnv(conf *cfg.Config) {
|
||||
contexts := []cfg.Apicontext{}
|
||||
|
||||
for _, envvar := range os.Environ() {
|
||||
pair := strings.SplitN(envvar, "=", 2)
|
||||
if strings.HasPrefix(pair[0], "EPHEMERUPD_CONTEXT_") {
|
||||
c := strings.SplitN(pair[1], ":", 2)
|
||||
if len(c) == 2 {
|
||||
contexts = append(contexts, cfg.Apicontext{Context: c[0], Key: c[1]})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contexts = append(contexts, conf.Apicontexts...)
|
||||
|
||||
conf.Apicontexts = contexts
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDuration2Seconds(t *testing.T) {
|
||||
var tests = []struct {
|
||||
dur string
|
||||
expect int
|
||||
}{
|
||||
{"1d", 60 * 60 * 24},
|
||||
{"1h", 60 * 60},
|
||||
{"10m", 60 * 10},
|
||||
{"2h4m10s", (60 * 120) + (4 * 60) + 10},
|
||||
{"88u", 0},
|
||||
{"19t77X what?4s", 4},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("duration-%s", tt.dur)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
seconds := duration2int(tt.dur)
|
||||
if seconds != tt.expect {
|
||||
t.Errorf("got %d, want %d", seconds, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsExpired(t *testing.T) {
|
||||
var tests = []struct {
|
||||
expire string
|
||||
start time.Time
|
||||
expect bool
|
||||
}{
|
||||
{"3s", time.Now(), true},
|
||||
{"1d", time.Now(), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("isexpired-%s-%s", tt.start, tt.expire)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
time.Sleep(5 * time.Second)
|
||||
got := IsExpired(tt.start, tt.expire)
|
||||
if got != tt.expect {
|
||||
t.Errorf("got %t, want %t", got, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntaint(t *testing.T) {
|
||||
var tests = []struct {
|
||||
want string
|
||||
input string
|
||||
expect string
|
||||
wanterr bool
|
||||
}{
|
||||
{`[^a-zA-Z0-9\-]`, "ab23-bb43-beef", "ab23-bb43-beef", false},
|
||||
{`[^a-zA-Z0-9\-]`, "`cat passwd`+ab23-bb43-beef", "catpasswdab23-bb43-beef", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("untaint-%s-%s", tt.want, tt.expect)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
untainted, err := Untaint(tt.input, tt.want)
|
||||
if untainted != tt.expect {
|
||||
t.Errorf("got %s, want %s", untainted, tt.expect)
|
||||
}
|
||||
if err != nil && !tt.wanterr {
|
||||
t.Errorf("got error: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module codeberg.org/scip/ephemerup/common
|
||||
|
||||
go 1.18
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package common
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// https://gist.github.com/rhcarvalho/9338c3ff8850897c68bc74797c5dc25b
|
||||
|
||||
// Timestamp is like time.Time, but knows how to unmarshal from JSON
|
||||
// Unix timestamp numbers or RFC3339 strings, and marshal back into
|
||||
// the same JSON representation.
|
||||
type Timestamp struct {
|
||||
time.Time
|
||||
rfc3339 bool
|
||||
}
|
||||
|
||||
func (t Timestamp) MarshalJSON() ([]byte, error) {
|
||||
if t.rfc3339 {
|
||||
return t.Time.MarshalJSON()
|
||||
}
|
||||
return t.formatUnix()
|
||||
}
|
||||
|
||||
func (t *Timestamp) UnmarshalJSON(data []byte) error {
|
||||
err := t.Time.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return t.parseUnix(data)
|
||||
}
|
||||
t.rfc3339 = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Timestamp) formatUnix() ([]byte, error) {
|
||||
sec := float64(t.Time.UnixNano()) * float64(time.Nanosecond) / float64(time.Second)
|
||||
return strconv.AppendFloat(nil, sec, 'f', -1, 64), nil
|
||||
}
|
||||
|
||||
func (t *Timestamp) parseUnix(data []byte) error {
|
||||
f, err := strconv.ParseFloat(string(data), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Time = time.Unix(0, int64(f*float64(time.Second/time.Nanosecond)))
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
We could use time.ParseDuration(), but this doesn't support days.
|
||||
|
||||
We could also use github.com/xhit/go-str2duration/v2, which does
|
||||
the job, but it's just another dependency, just for this little
|
||||
gem. And we don't need a time.Time value.
|
||||
|
||||
Convert a duration into seconds (int).
|
||||
Valid time units are "s", "m", "h" and "d".
|
||||
*/
|
||||
func Duration2int(duration string) int {
|
||||
re := regexp.MustCompile(`(\d+)([dhms])`)
|
||||
seconds := 0
|
||||
|
||||
for _, match := range re.FindAllStringSubmatch(duration, -1) {
|
||||
if len(match) == 3 {
|
||||
v, _ := strconv.Atoi(match[1])
|
||||
switch match[2][0] {
|
||||
case 'd':
|
||||
seconds += v * 86400
|
||||
case 'h':
|
||||
seconds += v * 3600
|
||||
case 'm':
|
||||
seconds += v * 60
|
||||
case 's':
|
||||
seconds += v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seconds
|
||||
}
|
||||
219
common/types.go
219
common/types.go
@@ -1,219 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// used to return to the api client
|
||||
type Result struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
// upload or form structs
|
||||
type Dbentry interface {
|
||||
Getcontext(j []byte) (string, error)
|
||||
Marshal() ([]byte, error)
|
||||
MatchExpire(r *regexp.Regexp) bool
|
||||
MatchDescription(r *regexp.Regexp) bool
|
||||
MatchFile(r *regexp.Regexp) bool
|
||||
MatchCreated(r *regexp.Regexp) bool
|
||||
IsType(t int) bool
|
||||
}
|
||||
|
||||
type Upload struct {
|
||||
Type int `json:"type"`
|
||||
Id string `json:"id"`
|
||||
Expire string `json:"expire"`
|
||||
File string `json:"file"` // final filename (visible to the downloader)
|
||||
Members []string `json:"members"` // contains multiple files, so File is an archive
|
||||
Created Timestamp `json:"uploaded"`
|
||||
Context string `json:"context"`
|
||||
Description string `json:"description"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// this one is also used for marshalling to the client
|
||||
type Response struct {
|
||||
Uploads []*Upload `json:"uploads"`
|
||||
Forms []*Form `json:"forms"`
|
||||
|
||||
// integrate the Result struct so we can signal success
|
||||
Result
|
||||
}
|
||||
|
||||
type Form struct {
|
||||
// Note the dual use of the Id: it will be used as onetime api key
|
||||
// from generated upload forms and stored in the session store so
|
||||
// that the upload handler is able to check if the form object has
|
||||
// to be deleted immediately (if its expire field has been set to
|
||||
// asap)
|
||||
Type int `json:"type"`
|
||||
Id string `json:"id"`
|
||||
Expire string `json:"expire"`
|
||||
Description string `json:"description"`
|
||||
Created Timestamp `json:"uploaded"`
|
||||
Context string `json:"context"`
|
||||
Url string `json:"url"`
|
||||
Notify string `json:"notify"`
|
||||
}
|
||||
|
||||
const (
|
||||
TypeUpload = iota
|
||||
TypeForm
|
||||
)
|
||||
|
||||
/*
|
||||
implement Dbentry interface
|
||||
*/
|
||||
func (upload Upload) Getcontext(j []byte) (string, error) {
|
||||
if err := json.Unmarshal(j, &upload); err != nil {
|
||||
return "", fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
return upload.Context, nil
|
||||
}
|
||||
|
||||
func (form Form) Getcontext(j []byte) (string, error) {
|
||||
if err := json.Unmarshal(j, &form); err != nil {
|
||||
return "", fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
return form.Context, nil
|
||||
}
|
||||
|
||||
func (upload Upload) Marshal() ([]byte, error) {
|
||||
jsonentry, err := json.Marshal(upload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json marshalling failure: %s", err)
|
||||
}
|
||||
|
||||
return jsonentry, nil
|
||||
}
|
||||
|
||||
func (form Form) Marshal() ([]byte, error) {
|
||||
jsonentry, err := json.Marshal(form)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json marshalling failure: %s", err)
|
||||
}
|
||||
|
||||
return jsonentry, nil
|
||||
}
|
||||
|
||||
func (upload Upload) MatchExpire(r *regexp.Regexp) bool {
|
||||
return r.MatchString(upload.Expire)
|
||||
}
|
||||
|
||||
func (upload Upload) MatchDescription(r *regexp.Regexp) bool {
|
||||
return r.MatchString(upload.Description)
|
||||
}
|
||||
|
||||
func (upload Upload) MatchCreated(r *regexp.Regexp) bool {
|
||||
return r.MatchString(upload.Created.Time.String())
|
||||
}
|
||||
|
||||
func (upload Upload) MatchFile(r *regexp.Regexp) bool {
|
||||
return r.MatchString(upload.File)
|
||||
}
|
||||
|
||||
func (form Form) MatchExpire(r *regexp.Regexp) bool {
|
||||
return r.MatchString(form.Expire)
|
||||
}
|
||||
|
||||
func (form Form) MatchDescription(r *regexp.Regexp) bool {
|
||||
return r.MatchString(form.Description)
|
||||
}
|
||||
|
||||
func (form Form) MatchCreated(r *regexp.Regexp) bool {
|
||||
return r.MatchString(form.Created.Time.String())
|
||||
}
|
||||
|
||||
func (form Form) MatchFile(r *regexp.Regexp) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (upload Upload) IsType(t int) bool {
|
||||
if upload.Type == t {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (form Form) IsType(t int) bool {
|
||||
if form.Type == t {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
Response methods
|
||||
*/
|
||||
func (r *Response) Append(entry Dbentry) {
|
||||
switch entry.(type) {
|
||||
case *Upload:
|
||||
r.Uploads = append(r.Uploads, entry.(*Upload))
|
||||
case Upload:
|
||||
r.Uploads = append(r.Uploads, entry.(*Upload))
|
||||
case Form:
|
||||
r.Forms = append(r.Forms, entry.(*Form))
|
||||
case *Form:
|
||||
r.Forms = append(r.Forms, entry.(*Form))
|
||||
default:
|
||||
panic("unknown type!")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Extract context, whatever kind entry is, but we don't know in
|
||||
advance, only after unmarshalling. So try Upload first, if that
|
||||
fails, try Form.
|
||||
*/
|
||||
func GetContext(j []byte) (string, error) {
|
||||
upload := &Upload{}
|
||||
entryContext, err := upload.Getcontext(j)
|
||||
if err != nil {
|
||||
form := &Form{}
|
||||
entryContext, err = form.Getcontext(j)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return entryContext, nil
|
||||
}
|
||||
|
||||
func Unmarshal(j []byte, t int) (Dbentry, error) {
|
||||
if t == TypeUpload {
|
||||
upload := &Upload{}
|
||||
if err := json.Unmarshal(j, &upload); err != nil {
|
||||
return upload, fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
return upload, nil
|
||||
} else {
|
||||
form := &Form{}
|
||||
if err := json.Unmarshal(j, &form); err != nil {
|
||||
return form, fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
return form, nil
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
/*
|
||||
Untaint user input, that is: remove all non supported chars.
|
||||
|
||||
wanted is a regexp matching chars we shall leave. Everything else
|
||||
will be removed. Eg:
|
||||
|
||||
untainted := Untaint(input, `[^a-zA-Z0-9\-]`)
|
||||
|
||||
Returns a new string and an error if the input string has been
|
||||
modified. It's the callers choice to decide what to do about
|
||||
it. You may ignore the error and use the untainted string or bail
|
||||
out.
|
||||
*/
|
||||
func Untaint(input string, wanted *regexp.Regexp) (string, error) {
|
||||
untainted := wanted.ReplaceAllString(input, "")
|
||||
|
||||
if len(untainted) != len(input) {
|
||||
return untainted, errors.New("Invalid input string!")
|
||||
}
|
||||
|
||||
return untainted, nil
|
||||
}
|
||||
BIN
demo/upctl.gif
BIN
demo/upctl.gif
Binary file not shown.
|
Before Width: | Height: | Size: 636 KiB |
@@ -1,6 +0,0 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
ephemerup:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
@@ -1,26 +0,0 @@
|
||||
# -*-ruby-*-
|
||||
listen = ":8080"
|
||||
bodylimit = 10000
|
||||
|
||||
apicontexts = [
|
||||
{
|
||||
context = "root"
|
||||
key = "0fddbff5d8010f81cd28a7d77f3e38981b13d6164c2fd6e1c3f60a4287630c37",
|
||||
},
|
||||
{
|
||||
context = "foo",
|
||||
key = "970b391f22f515d96b3e9b86a2c62c627968828e47b356994d2e583188b4190a"
|
||||
}
|
||||
]
|
||||
|
||||
#url = "https://sokrates.daemon.de"
|
||||
|
||||
# this is the root context with all permissions
|
||||
super = "root"
|
||||
|
||||
mail = {
|
||||
server = "localhost"
|
||||
port = "25"
|
||||
from = "root@localhost"
|
||||
password = ""
|
||||
}
|
||||
46
go.mod
46
go.mod
@@ -1,46 +0,0 @@
|
||||
module codeberg.org/scip/ephemerup
|
||||
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
codeberg.org/scip/ephemerup/common v0.0.0-20231001134723-a1b3fe6f25d1
|
||||
github.com/alecthomas/repr v0.2.0
|
||||
github.com/gofiber/fiber/v2 v2.42.0
|
||||
github.com/gofiber/keyauth/v2 v2.1.32
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/knadh/koanf/parsers/hcl v0.1.0
|
||||
github.com/knadh/koanf/providers/env v0.1.0
|
||||
github.com/knadh/koanf/providers/file v0.1.0
|
||||
github.com/knadh/koanf/providers/posflag v0.1.0
|
||||
github.com/knadh/koanf/v2 v2.0.1
|
||||
github.com/maxatome/go-testdeep v1.13.0
|
||||
github.com/spf13/pflag v1.0.10
|
||||
go.etcd.io/bbolt v1.3.12
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 // indirect
|
||||
github.com/tinylib/msgp v1.1.9 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.44.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
)
|
||||
|
||||
replace codeberg.org/scip/ephemerup/common => ./common
|
||||
135
go.sum
135
go.sum
@@ -1,135 +0,0 @@
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
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/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8=
|
||||
github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc=
|
||||
github.com/gofiber/keyauth/v2 v2.1.32 h1:ExnCEUlgF4pQn8BLPa4VMVR12R78KtrJe0h4SqQAK5Q=
|
||||
github.com/gofiber/keyauth/v2 v2.1.32/go.mod h1:zeJzlvfvjMH31A1b0NaK3FkO7mCoOiK02Xl748XHHNI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/hcl v0.1.0 h1:PuAAdRMXbxmhwzZftiQBEtWIKc3EbRHk/Fi+olo02z4=
|
||||
github.com/knadh/koanf/parsers/hcl v0.1.0/go.mod h1:7ClRvH1oP5ne8SfaDZZBK28/o9r4rek0PC4Vrc8qdvE=
|
||||
github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
|
||||
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
|
||||
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
|
||||
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
|
||||
github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U=
|
||||
github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0=
|
||||
github.com/knadh/koanf/v2 v2.0.0 h1:XPQ5ilNnwnNaHrfQ1YpTVhUAjcGHnEKA+lRpipQv02Y=
|
||||
github.com/knadh/koanf/v2 v2.0.0/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
|
||||
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
|
||||
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/maxatome/go-testdeep v1.13.0 h1:EBmRelH7MhMfPvA+0kXAeOeJUXn3mzul5NmvjLDcQZI=
|
||||
github.com/maxatome/go-testdeep v1.13.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo=
|
||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||
github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 h1:McifyVxygw1d67y6vxUqls2D46J8W9nrki9c8c0eVvE=
|
||||
github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761/go.mod h1:Vi9gvHvTw4yCUHIznFl5TPULS7aXwgaTByGeBY75Wko=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
|
||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=
|
||||
github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q=
|
||||
github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.etcd.io/bbolt v1.3.12 h1:UAxZAIuJqzFwByP19gZC3zd5robK3FOangrGS+Fdczg=
|
||||
go.etcd.io/bbolt v1.3.12/go.mod h1:Gi2toLZr1jFkuReJm+yEPn7H8wk6ooptePtHYCbCS1g=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
30
main.go
30
main.go
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"codeberg.org/scip/ephemerup/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
84
mkrel.sh
84
mkrel.sh
@@ -1,84 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright © 2023 Thomas von Dein
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# get list with: go tool dist list
|
||||
DIST="darwin/amd64
|
||||
freebsd/amd64
|
||||
linux/amd64
|
||||
windows/amd64"
|
||||
|
||||
daemon="$1"
|
||||
client="$2"
|
||||
version="$3"
|
||||
|
||||
if test -z "$version"; then
|
||||
echo "Usage: $0 <daemon name> <client name> <release version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf releases
|
||||
mkdir -p releases
|
||||
|
||||
|
||||
for D in $DIST; do
|
||||
os=${D/\/*/}
|
||||
arch=${D/*\//}
|
||||
binfile="releases/${daemon}-${os}-${arch}-${version}"
|
||||
clientfile="releases/${client}-${os}-${arch}-${version}"
|
||||
tardir="${daemon}-${os}-${arch}-${version}"
|
||||
tarfile="releases/${daemon}-${os}-${arch}-${version}.tar.gz"
|
||||
set -x
|
||||
|
||||
GOOS=${os} GOARCH=${arch} CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags \
|
||||
"-extldflags=-static -s -X 'codeberg.org/scip/ephemerup/cfg.VERSION=${version}'" -o ${binfile}
|
||||
#GOOS=${os} GOARCH=${arch} go build -o ${binfile} -ldflags "-X 'codeberg.org/scip/ephemerup/cfg.VERSION=${version}'"
|
||||
cd $client
|
||||
GOOS=${os} GOARCH=${arch} go build -o ../${clientfile} -ldflags \
|
||||
"-X 'codeberg.org/scip/ephemerup/upctl/cfg.VERSION=${version}'"
|
||||
cd -
|
||||
|
||||
mkdir -p ${tardir}
|
||||
cp ${binfile} ${clientfile} README.md LICENSE ${tardir}/
|
||||
|
||||
echo 'daemon = ephemerupd
|
||||
client = upctl
|
||||
PREFIX = /usr/local
|
||||
UID = root
|
||||
GID = 0
|
||||
|
||||
install-client:
|
||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
||||
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
|
||||
install -o $(UID) -g $(GID) -m 555 $(client) $(PREFIX)/bin/
|
||||
install -o $(UID) -g $(GID) -m 444 $(client).1 $(PREFIX)/man/man1/
|
||||
|
||||
install: install-client
|
||||
install -d -o $(UID) -g $(GID) $(PREFIX)/sbin
|
||||
install -o $(UID) -g $(GID) -m 555 $(daemon) $(PREFIX)/sbin/
|
||||
install -o $(UID) -g $(GID) -m 444 $(daemon).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile
|
||||
|
||||
tar cpzf ${tarfile} ${tardir}
|
||||
|
||||
for file in ${binfile} ${tarfile} ${clientfile}; do
|
||||
sha256sum ${file} | cut -d' ' -f1 > ${file}.sha256
|
||||
done
|
||||
|
||||
rm -rf ${tardir}
|
||||
set +x
|
||||
done
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*-ruby-*-
|
||||
listen = ":8080"
|
||||
bodylimit = 10001
|
||||
@@ -1,102 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- -*-web-*- -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="description" content="upload form" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>File upload form</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet" integrity="sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h4>Upload form {{ .Id }}</h4>
|
||||
<!-- Response -->
|
||||
<div class="statusMsg"></div>
|
||||
|
||||
<!-- File upload form -->
|
||||
<div class="col-lg-12">
|
||||
<form id="UploadForm" enctype="multipart/form-data" action="/v1/uploads" method="POST">
|
||||
<div class="mb-3 row">
|
||||
<p>
|
||||
Use this form to upload one or more files. The creator of the form will automatically get notified.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-2 col-form-label">Description</label>
|
||||
<label class="col-sm-10 col-form-label">{{ .Description}} </label>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label for="file" class="col-sm-2 col-form-label">Select</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="file" class="form-control" id="file" name="uploads[]" multiple
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<label for="display" class="col-sm-2 col-form-label">Selected Files</label>
|
||||
<div class="col-sm-10">
|
||||
<!-- <input type="textara" class="form-control" id="upload-file-info" readonly>-->
|
||||
<div id="upload-file-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="submit" name="submit" class="btn btn-success submitBtn" value="Upload"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
// Submit form data via Ajax
|
||||
$("#UploadForm").on('submit', function(e){
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/v1/uploads',
|
||||
data: new FormData(this),
|
||||
dataType: 'json',
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function(xhr){
|
||||
$('.submitBtn').attr("disabled","disabled");
|
||||
$('#UploadForm').css("opacity",".5");
|
||||
xhr.setRequestHeader('Authorization', 'Bearer {{.Id}}');
|
||||
},
|
||||
success: function(response){
|
||||
$('.statusMsg').html('');
|
||||
if(response.success){
|
||||
$('#UploadForm')[0].reset();
|
||||
$('.statusMsg').html('<p class="alert alert-success">Your upload is available for download.<!-- '
|
||||
+response.uploads[0].url+' -->');
|
||||
$('#UploadForm').hide();
|
||||
}else{
|
||||
$('.statusMsg').html('<p class="alert alert-danger">'+response.message+'</p>');
|
||||
}
|
||||
$('#UploadForm').css("opacity","");
|
||||
$(".submitBtn").removeAttr("disabled");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#file").on('change', function() {
|
||||
$("#upload-file-info").empty();
|
||||
for (var i = 0; i < $(this).get(0).files.length; ++i) {
|
||||
$("#upload-file-info").append('<i class="bi-check-lg"></i> ' + $(this).get(0).files[i].name + '<br>');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
|
||||
# Copyright © 2023 Thomas von Dein
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#
|
||||
# no need to modify anything below
|
||||
tool = upctl
|
||||
version = $(shell egrep "= .v" cfg/config.go | cut -d'=' -f2 | cut -d'"' -f 2)
|
||||
archs = android darwin freebsd linux netbsd openbsd windows
|
||||
PREFIX = /usr/local
|
||||
UID = root
|
||||
GID = 0
|
||||
BRANCH = $(shell git branch --show-current)
|
||||
COMMIT = $(shell git rev-parse --short=8 HEAD)
|
||||
BUILD = $(shell date +%Y.%m.%d.%H%M%S)
|
||||
VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version))
|
||||
HAVE_POD := $(shell pod2text -h 2>/dev/null)
|
||||
HAVE_LINT:= $(shell golangci-lint -h 2>/dev/null)
|
||||
|
||||
all: lint buildlocal
|
||||
|
||||
lint:
|
||||
ifdef HAVE_LINT
|
||||
golangci-lint run
|
||||
endif
|
||||
|
||||
buildlocal:
|
||||
go build -ldflags "-X 'codeberg.org/scip/ephemerup/upctl/cfg.VERSION=$(VERSION)'"
|
||||
|
||||
release:
|
||||
./mkrel.sh $(tool) $(version)
|
||||
gh release create $(version) --generate-notes releases/*
|
||||
|
||||
install: buildlocal
|
||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
||||
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
|
||||
|
||||
clean:
|
||||
rm -rf $(tool) releases coverage.out
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
singletest:
|
||||
@echo "Call like this: ''make singletest TEST=TestX1 MOD=lib"
|
||||
go test -run $(TEST) codeberg.org/scip/upctl/$(MOD)
|
||||
|
||||
cover-report:
|
||||
go test ./... -cover -coverprofile=coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
show-versions: buildlocal
|
||||
@echo "### upctl version:"
|
||||
@./upctl --version
|
||||
|
||||
@echo
|
||||
@echo "### go module versions:"
|
||||
@go list -m all
|
||||
|
||||
@echo
|
||||
@echo "### go version used for building:"
|
||||
@grep -m 1 go go.mod
|
||||
|
||||
goupdate:
|
||||
go get -t -u=patch ./...
|
||||
@@ -1,14 +0,0 @@
|
||||
## upload control client (upctl)
|
||||
|
||||
```
|
||||
upctl access.log
|
||||
Uploaded: access.log
|
||||
Expires: after first access
|
||||
Download URL: http://localhost:8080/api/getfile/cc70324b-5c8e-4ca2-bb32-10aa0eeb6dbf/2023-02-14-19-18-access.log
|
||||
|
||||
upctl access.log error.log
|
||||
Uploaded: access.log
|
||||
Uploaded: error.log
|
||||
Expires: after first access
|
||||
Download URL: http://localhost:8080/api/getfile/cc70324b-5c8e-4ca2-bb32-10aa0eeb6dbf/2023-02-14-19-18-data.zip
|
||||
```
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
//"strings"
|
||||
)
|
||||
|
||||
const Version string = "v0.0.1"
|
||||
|
||||
var VERSION string // maintained by -x
|
||||
|
||||
type Config struct {
|
||||
// globals
|
||||
Endpoint string
|
||||
Debug bool
|
||||
Retries int
|
||||
Silent bool
|
||||
|
||||
// used for authentication
|
||||
Apikey string
|
||||
|
||||
// upload
|
||||
Expire string
|
||||
|
||||
// used for filtering (list command)
|
||||
Apicontext string
|
||||
|
||||
// required to intercept requests using httpmock in tests
|
||||
Mock bool
|
||||
|
||||
// used to filter lists
|
||||
Query string
|
||||
|
||||
// required for forms
|
||||
Description string
|
||||
Notify string
|
||||
}
|
||||
|
||||
func Getversion() string {
|
||||
// main program version
|
||||
|
||||
// generated version string, used by -v contains cfg.Version on
|
||||
// main branch, and cfg.Version-$branch-$lastcommit-$date on
|
||||
// development branch
|
||||
|
||||
return fmt.Sprintf("This is upctl version %s", VERSION)
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//"errors"
|
||||
"errors"
|
||||
"github.com/spf13/cobra"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
"codeberg.org/scip/ephemerup/upctl/cfg"
|
||||
"codeberg.org/scip/ephemerup/upctl/lib"
|
||||
"os"
|
||||
)
|
||||
|
||||
func FormCommand(conf *cfg.Config) *cobra.Command {
|
||||
var formCmd = &cobra.Command{
|
||||
Use: "form {create|delete|modify|list}",
|
||||
Short: "Form commands",
|
||||
Long: `Manage upload forms.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
//cmd.SilenceUsage = true
|
||||
if len(args) == 0 {
|
||||
return cmd.Help()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
formCmd.Aliases = append(formCmd.Aliases, "frm")
|
||||
formCmd.Aliases = append(formCmd.Aliases, "f")
|
||||
|
||||
formCmd.AddCommand(FormCreateCommand(conf))
|
||||
formCmd.AddCommand(FormListCommand(conf))
|
||||
formCmd.AddCommand(FormDeleteCommand(conf))
|
||||
formCmd.AddCommand(FormDescribeCommand(conf))
|
||||
formCmd.AddCommand(FormModifyCommand(conf))
|
||||
|
||||
return formCmd
|
||||
}
|
||||
|
||||
func FormCreateCommand(conf *cfg.Config) *cobra.Command {
|
||||
var formCreateCmd = &cobra.Command{
|
||||
Use: "create [options]",
|
||||
Short: "Create a new form",
|
||||
Long: `Create a new form for consumers so they can upload something.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.CreateForm(os.Stdout, conf)
|
||||
},
|
||||
}
|
||||
|
||||
// options
|
||||
formCreateCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "",
|
||||
"Expire setting: asap or duration (accepted shortcuts: dmh)")
|
||||
formCreateCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "",
|
||||
"Description of the form")
|
||||
formCreateCmd.PersistentFlags().StringVarP(&conf.Notify, "notify", "n", "",
|
||||
"Email address to get notified when consumer has uploaded files")
|
||||
|
||||
formCreateCmd.Aliases = append(formCreateCmd.Aliases, "add")
|
||||
formCreateCmd.Aliases = append(formCreateCmd.Aliases, "+")
|
||||
|
||||
return formCreateCmd
|
||||
}
|
||||
|
||||
func FormModifyCommand(conf *cfg.Config) *cobra.Command {
|
||||
var formModifyCmd = &cobra.Command{
|
||||
Use: "modify [options] <id>",
|
||||
Short: "Modify a form",
|
||||
Long: `Modify an existing form.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return cmd.Help()
|
||||
}
|
||||
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.Modify(os.Stdout, conf, args, common.TypeForm)
|
||||
},
|
||||
}
|
||||
|
||||
// options
|
||||
formModifyCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "",
|
||||
"Expire setting: asap or duration (accepted shortcuts: dmh)")
|
||||
formModifyCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "",
|
||||
"Description of the form")
|
||||
formModifyCmd.PersistentFlags().StringVarP(&conf.Notify, "notify", "n", "",
|
||||
"Email address to get notified when consumer has uploaded files")
|
||||
|
||||
formModifyCmd.Aliases = append(formModifyCmd.Aliases, "mod")
|
||||
formModifyCmd.Aliases = append(formModifyCmd.Aliases, "change")
|
||||
|
||||
return formModifyCmd
|
||||
}
|
||||
|
||||
func FormListCommand(conf *cfg.Config) *cobra.Command {
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list [options]",
|
||||
Short: "List formss",
|
||||
Long: `List formss.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.List(os.Stdout, conf, nil, common.TypeForm)
|
||||
},
|
||||
}
|
||||
|
||||
// options
|
||||
listCmd.PersistentFlags().StringVarP(&conf.Apicontext, "apicontext", "", "", "Filter by given API context")
|
||||
listCmd.PersistentFlags().StringVarP(&conf.Query, "query", "q", "", "Filter by given query regexp")
|
||||
|
||||
listCmd.Aliases = append(listCmd.Aliases, "ls")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "l")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
func FormDeleteCommand(conf *cfg.Config) *cobra.Command {
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete [options] <id>",
|
||||
Short: "Delete an form",
|
||||
Long: `Delete an form identified by its id`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id specified to delete!")
|
||||
}
|
||||
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.Delete(os.Stdout, conf, args, common.TypeForm)
|
||||
},
|
||||
}
|
||||
|
||||
deleteCmd.Aliases = append(deleteCmd.Aliases, "rm")
|
||||
deleteCmd.Aliases = append(deleteCmd.Aliases, "d")
|
||||
|
||||
return deleteCmd
|
||||
}
|
||||
|
||||
func FormDescribeCommand(conf *cfg.Config) *cobra.Command {
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "describe [options] form-id",
|
||||
Long: "Show detailed informations about an form object.",
|
||||
Short: `Describe an form.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id specified to delete!")
|
||||
}
|
||||
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.Describe(os.Stdout, conf, args, common.TypeForm)
|
||||
},
|
||||
}
|
||||
|
||||
listCmd.Aliases = append(listCmd.Aliases, "des")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "info")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "i")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/spf13/cobra"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
"codeberg.org/scip/ephemerup/upctl/cfg"
|
||||
"codeberg.org/scip/ephemerup/upctl/lib"
|
||||
"os"
|
||||
)
|
||||
|
||||
func UploadCommand(conf *cfg.Config) *cobra.Command {
|
||||
var uploadCmd = &cobra.Command{
|
||||
Use: "upload [options] [file ..]",
|
||||
Short: "Upload files",
|
||||
Long: `Upload files to an upload api.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No files specified to upload!")
|
||||
}
|
||||
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.UploadFiles(os.Stdout, conf, args)
|
||||
},
|
||||
}
|
||||
|
||||
// options
|
||||
uploadCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "", "Expire setting: asap or duration (accepted shortcuts: dmh)")
|
||||
uploadCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "", "Description of the form")
|
||||
|
||||
uploadCmd.Aliases = append(uploadCmd.Aliases, "up")
|
||||
uploadCmd.Aliases = append(uploadCmd.Aliases, "u")
|
||||
|
||||
return uploadCmd
|
||||
}
|
||||
|
||||
func ListCommand(conf *cfg.Config) *cobra.Command {
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list [options] [file ..]",
|
||||
Short: "List uploads",
|
||||
Long: `List uploads`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.List(os.Stdout, conf, args, common.TypeUpload)
|
||||
},
|
||||
}
|
||||
|
||||
// options
|
||||
listCmd.PersistentFlags().StringVarP(&conf.Apicontext, "apicontext", "", "", "Filter by given API context")
|
||||
listCmd.PersistentFlags().StringVarP(&conf.Query, "query", "q", "", "Filter by given query regexp")
|
||||
|
||||
listCmd.Aliases = append(listCmd.Aliases, "ls")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "l")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
func DeleteCommand(conf *cfg.Config) *cobra.Command {
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete [options] <id>",
|
||||
Short: "Delete an upload",
|
||||
Long: `Delete an upload identified by its id`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id specified to delete!")
|
||||
}
|
||||
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.Delete(os.Stdout, conf, args, common.TypeUpload)
|
||||
},
|
||||
}
|
||||
|
||||
deleteCmd.Aliases = append(deleteCmd.Aliases, "rm")
|
||||
deleteCmd.Aliases = append(deleteCmd.Aliases, "d")
|
||||
|
||||
return deleteCmd
|
||||
}
|
||||
|
||||
func DescribeCommand(conf *cfg.Config) *cobra.Command {
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "describe [options] upload-id",
|
||||
Long: "Show detailed informations about an upload object.",
|
||||
Short: `Describe an upload`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id specified to delete!")
|
||||
}
|
||||
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.Describe(os.Stdout, conf, args, common.TypeUpload)
|
||||
},
|
||||
}
|
||||
|
||||
listCmd.Aliases = append(listCmd.Aliases, "des")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "info")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "i")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
func DownloadCommand(conf *cfg.Config) *cobra.Command {
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "download [options] upload-id",
|
||||
Long: "Download the file associated with an upload object.",
|
||||
Short: `Download a file`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id specified to delete!")
|
||||
}
|
||||
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.Download(os.Stdout, conf, args)
|
||||
},
|
||||
}
|
||||
|
||||
listCmd.Aliases = append(listCmd.Aliases, "down")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "get")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "g")
|
||||
listCmd.Aliases = append(listCmd.Aliases, "fetch")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
func ModifyCommand(conf *cfg.Config) *cobra.Command {
|
||||
var uploadModifyCmd = &cobra.Command{
|
||||
Use: "modify [options] <id>",
|
||||
Short: "Modify an upload",
|
||||
Long: `Modify an existing upload.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return cmd.Help()
|
||||
}
|
||||
|
||||
// errors at this stage do not cause the usage to be shown
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return lib.Modify(os.Stdout, conf, args, common.TypeUpload)
|
||||
},
|
||||
}
|
||||
|
||||
// options
|
||||
uploadModifyCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "",
|
||||
"Expire setting: asap or duration (accepted shortcuts: dmh)")
|
||||
uploadModifyCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "",
|
||||
"Description of the upload")
|
||||
|
||||
uploadModifyCmd.Aliases = append(uploadModifyCmd.Aliases, "mod")
|
||||
uploadModifyCmd.Aliases = append(uploadModifyCmd.Aliases, "change")
|
||||
|
||||
return uploadModifyCmd
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"codeberg.org/scip/ephemerup/upctl/cfg"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
)
|
||||
|
||||
func completion(cmd *cobra.Command, mode string) error {
|
||||
switch mode {
|
||||
case "bash":
|
||||
return cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
return cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
return cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
default:
|
||||
return errors.New("Invalid shell parameter! Valid ones: bash|zsh|fish|powershell")
|
||||
}
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
var (
|
||||
conf cfg.Config
|
||||
ShowVersion bool
|
||||
ShowCompletion string
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "upctl [options]",
|
||||
Short: "upload api client",
|
||||
Long: `Manage files on an upload api server.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if ShowVersion {
|
||||
fmt.Println(cfg.Getversion())
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(ShowCompletion) > 0 {
|
||||
return completion(cmd, ShowCompletion)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return errors.New("No command specified!")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return initConfig(cmd, &conf)
|
||||
},
|
||||
}
|
||||
|
||||
// options
|
||||
rootCmd.PersistentFlags().BoolVarP(&ShowVersion, "version", "v", false, "Print program version")
|
||||
rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false, "Enable debugging")
|
||||
rootCmd.PersistentFlags().BoolVarP(&conf.Silent, "silent", "s", false, "Disable progress bar and other noise")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "custom config file")
|
||||
rootCmd.PersistentFlags().IntVarP(&conf.Retries, "retries", "r", 3, "How often shall we retry to access our endpoint")
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.Endpoint, "endpoint", "p", "http://localhost:8080/api/v1", "upload api endpoint url")
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.Apikey, "apikey", "a", "", "Api key to use")
|
||||
|
||||
rootCmd.AddCommand(UploadCommand(&conf))
|
||||
rootCmd.AddCommand(ListCommand(&conf))
|
||||
rootCmd.AddCommand(DeleteCommand(&conf))
|
||||
rootCmd.AddCommand(DescribeCommand(&conf))
|
||||
rootCmd.AddCommand(DownloadCommand(&conf))
|
||||
rootCmd.AddCommand(ModifyCommand(&conf))
|
||||
|
||||
// forms are being handled with its own subcommand
|
||||
rootCmd.AddCommand(FormCommand(&conf))
|
||||
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// initialize viper, read config and ENV, bind flags
|
||||
func initConfig(cmd *cobra.Command, cfg *cfg.Config) error {
|
||||
v := viper.New()
|
||||
viper.SetConfigType("hcl")
|
||||
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
v.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
v.SetConfigName("upctl")
|
||||
|
||||
// default location[s]
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath("$HOME/.config/upctl")
|
||||
}
|
||||
|
||||
// ignore read errors, report all others
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// fmt.Println("Using config file:", v.ConfigFileUsed())
|
||||
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvPrefix("upctl")
|
||||
|
||||
// map flags to viper
|
||||
return bindFlags(cmd, v)
|
||||
}
|
||||
|
||||
// bind flags to viper settings (env+cfgfile)
|
||||
func bindFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
var fail error
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
// map flag name to config variable
|
||||
configName := f.Name
|
||||
|
||||
// use config variable if flag is not set and config is set
|
||||
if !f.Changed && v.IsSet(configName) {
|
||||
val := v.Get(configName)
|
||||
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||
fail = err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return fail
|
||||
}
|
||||
46
upctl/go.mod
46
upctl/go.mod
@@ -1,46 +0,0 @@
|
||||
module codeberg.org/scip/ephemerup/upctl
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
codeberg.org/scip/ephemerup/common v0.0.0-20231001134723-a1b3fe6f25d1
|
||||
github.com/imroc/req/v3 v3.56.0
|
||||
github.com/jarcoal/httpmock v1.3.1
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/schollz/progressbar/v3 v3.13.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/icholy/digest v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.56.0 // indirect
|
||||
github.com/refraction-networking/utls v1.8.1 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace codeberg.org/scip/ephemerup/common => ../common
|
||||
552
upctl/go.sum
552
upctl/go.sum
@@ -1,552 +0,0 @@
|
||||
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.44.3/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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
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=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
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/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
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/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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.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/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.2/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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
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/martian/v3 v3.1.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/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
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 v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
|
||||
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
||||
github.com/imroc/req/v3 v3.56.0 h1:t6YdqqerYBXhZ9+VjqsQs5wlKxdUNEvsgBhxWc1AEEo=
|
||||
github.com/imroc/req/v3 v3.56.0/go.mod h1:cUZSooE8hhzFNOrAbdxuemXDQxFXLQTnu3066jr7ZGk=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
||||
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
||||
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY=
|
||||
github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c=
|
||||
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
|
||||
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
||||
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
|
||||
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
||||
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
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=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
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/lint v0.0.0-20201208152925-83fdc39ff7b5/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/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/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-20180826012351-8a410e7b638d/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-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/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-20201020160332-67f06af15bc9/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-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-20190502145724-3ef323f4f1fd/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-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-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
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/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
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/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
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/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/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
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/appengine v1.6.7/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/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/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/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
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=
|
||||
@@ -1,421 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
//"github.com/alecthomas/repr"
|
||||
"io"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
"codeberg.org/scip/ephemerup/upctl/cfg"
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
R *req.Request
|
||||
Url string
|
||||
}
|
||||
|
||||
type ListParams struct {
|
||||
Apicontext string `json:"apicontext"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
const Maxwidth = 12
|
||||
|
||||
/*
|
||||
Create a new request object for outgoing queries
|
||||
*/
|
||||
func Setup(c *cfg.Config, path string) *Request {
|
||||
client := req.C()
|
||||
if c.Debug {
|
||||
client.DevMode()
|
||||
}
|
||||
|
||||
client.SetUserAgent("upctl-" + cfg.VERSION)
|
||||
|
||||
R := client.R()
|
||||
|
||||
if c.Retries > 0 {
|
||||
// Enable retry and set the maximum retry count.
|
||||
R.SetRetryCount(c.Retries).
|
||||
// Set the retry sleep interval with a commonly used
|
||||
// algorithm: capped exponential backoff with jitter
|
||||
// (https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/).
|
||||
SetRetryBackoffInterval(1*time.Second, 5*time.Second).
|
||||
AddRetryHook(func(resp *req.Response, err error) {
|
||||
req := resp.Request.RawRequest
|
||||
if c.Debug {
|
||||
fmt.Println("Retrying endpoint request:", req.Method, req.URL, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if len(c.Apikey) > 0 {
|
||||
client.SetCommonBearerAuthToken(c.Apikey)
|
||||
}
|
||||
|
||||
if c.Mock {
|
||||
// intercept, used by unit tests
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
}
|
||||
|
||||
return &Request{Url: c.Endpoint + path, R: R}
|
||||
}
|
||||
|
||||
/*
|
||||
Iterate over args, considering the elements are filenames, and add
|
||||
them to the request.
|
||||
*/
|
||||
func GatherFiles(rq *Request, args []string) error {
|
||||
for _, file := range args {
|
||||
info, err := os.Stat(file)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
rq.R.SetFile("upload[]", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
rq.R.SetFile("upload[]", file)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Check HTTP Response Code and validate JSON status output, if
|
||||
any. Turns'em into a regular error
|
||||
*/
|
||||
func HandleResponse(c *cfg.Config, resp *req.Response) error {
|
||||
// we expect a json response, extract the error, if any
|
||||
r := Response{}
|
||||
|
||||
if c.Debug {
|
||||
trace := resp.Request.TraceInfo()
|
||||
fmt.Println(trace.Blame())
|
||||
fmt.Println("----------")
|
||||
fmt.Println(trace)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(resp.String()), &r); err != nil {
|
||||
// text output!
|
||||
r.Message = resp.String()
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
return fmt.Errorf("bad response: %s (%s)", resp.Status, r.Message)
|
||||
}
|
||||
|
||||
if !r.Success {
|
||||
if len(r.Message) == 0 {
|
||||
if resp.Err != nil {
|
||||
return resp.Err
|
||||
} else {
|
||||
return errors.New("Unknown error")
|
||||
}
|
||||
} else {
|
||||
return errors.New(r.Message)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UploadFiles(w io.Writer, c *cfg.Config, args []string) error {
|
||||
// setup url, req.Request, timeout handling etc
|
||||
rq := Setup(c, "/uploads")
|
||||
|
||||
// collect files to upload from @argv
|
||||
if err := GatherFiles(rq, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.Silent {
|
||||
// progres bar
|
||||
bar := progressbar.Default(100)
|
||||
var left float64
|
||||
rq.R.SetUploadCallbackWithInterval(func(info req.UploadInfo) {
|
||||
left = float64(info.UploadedSize) / float64(info.FileSize) * 100.0
|
||||
if err := bar.Add(int(left)); err != nil {
|
||||
fmt.Print("\r")
|
||||
}
|
||||
}, 10*time.Millisecond)
|
||||
}
|
||||
|
||||
// actual post w/ settings
|
||||
resp, err := rq.R.
|
||||
SetFormData(map[string]string{
|
||||
"expire": c.Expire,
|
||||
"description": c.Description,
|
||||
}).
|
||||
Post(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(w, resp)
|
||||
}
|
||||
|
||||
func List(w io.Writer, c *cfg.Config, args []string, typ int) error {
|
||||
var rq *Request
|
||||
|
||||
switch typ {
|
||||
case common.TypeUpload:
|
||||
rq = Setup(c, "/uploads")
|
||||
case common.TypeForm:
|
||||
rq = Setup(c, "/forms")
|
||||
}
|
||||
|
||||
params := &ListParams{Apicontext: c.Apicontext, Query: c.Query}
|
||||
resp, err := rq.R.
|
||||
SetBodyJsonMarshal(params).
|
||||
Get(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case common.TypeUpload:
|
||||
return UploadsRespondTable(w, resp)
|
||||
case common.TypeForm:
|
||||
return FormsRespondTable(w, resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Delete(w io.Writer, c *cfg.Config, args []string, typ int) error {
|
||||
for _, id := range args {
|
||||
var rq *Request
|
||||
caption := "Upload"
|
||||
|
||||
switch typ {
|
||||
case common.TypeUpload:
|
||||
rq = Setup(c, "/uploads/"+id)
|
||||
case common.TypeForm:
|
||||
rq = Setup(c, "/forms/"+id)
|
||||
caption = "Form"
|
||||
}
|
||||
|
||||
resp, err := rq.R.Delete(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s %s successfully deleted.\n", caption, id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Describe(w io.Writer, c *cfg.Config, args []string, typ int) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id provided!")
|
||||
}
|
||||
|
||||
var rq *Request
|
||||
id := args[0] // we describe only 1 object
|
||||
|
||||
switch typ {
|
||||
case common.TypeUpload:
|
||||
rq = Setup(c, "/uploads/"+id)
|
||||
case common.TypeForm:
|
||||
rq = Setup(c, "/forms/"+id)
|
||||
}
|
||||
|
||||
resp, err := rq.R.Get(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(w, resp)
|
||||
}
|
||||
|
||||
func Download(w io.Writer, c *cfg.Config, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("No id provided!")
|
||||
}
|
||||
|
||||
id := args[0]
|
||||
|
||||
rq := Setup(c, "/uploads/"+id+"/file")
|
||||
|
||||
if !c.Silent {
|
||||
// progres bar
|
||||
bar := progressbar.Default(100)
|
||||
|
||||
callback := func(info req.DownloadInfo) {
|
||||
if info.Response.Response != nil {
|
||||
if err := bar.Add(1); err != nil {
|
||||
fmt.Print("\r")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rq.R.SetDownloadCallback(callback)
|
||||
}
|
||||
|
||||
resp, err := rq.R.
|
||||
SetOutputFile(id).
|
||||
Get(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
return fmt.Errorf("bad response: %s", resp.Status)
|
||||
}
|
||||
|
||||
_, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
|
||||
if err != nil {
|
||||
os.Remove(id)
|
||||
return err
|
||||
}
|
||||
|
||||
filename := params["filename"]
|
||||
if filename == "" {
|
||||
os.Remove(id)
|
||||
return fmt.Errorf("No filename provided!")
|
||||
}
|
||||
|
||||
cleanfilename, _ := common.Untaint(filename, regexp.MustCompile(`[^a-zA-Z0-9\-\._]`))
|
||||
|
||||
if err := os.Rename(id, cleanfilename); err != nil {
|
||||
os.Remove(id)
|
||||
return fmt.Errorf("\nUnable to rename file: %s", err.Error())
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s successfully downloaded to file %s.", id, cleanfilename)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Modify(w io.Writer, c *cfg.Config, args []string, typ int) error {
|
||||
id := args[0]
|
||||
var rq *Request
|
||||
|
||||
// setup url, req.Request, timeout handling etc
|
||||
switch typ {
|
||||
case common.TypeUpload:
|
||||
rq = Setup(c, "/uploads/"+id)
|
||||
rq.R.
|
||||
SetBody(&common.Upload{
|
||||
Expire: c.Expire,
|
||||
Description: c.Description,
|
||||
})
|
||||
case common.TypeForm:
|
||||
rq = Setup(c, "/forms/"+id)
|
||||
rq.R.
|
||||
SetBody(&common.Form{
|
||||
Expire: c.Expire,
|
||||
Description: c.Description,
|
||||
Notify: c.Notify,
|
||||
})
|
||||
}
|
||||
|
||||
// actual put w/ settings
|
||||
resp, err := rq.R.Put(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(w, resp)
|
||||
}
|
||||
|
||||
/**** Forms stuff ****/
|
||||
func CreateForm(w io.Writer, c *cfg.Config) error {
|
||||
// setup url, req.Request, timeout handling etc
|
||||
rq := Setup(c, "/forms")
|
||||
|
||||
// actual post w/ settings
|
||||
resp, err := rq.R.
|
||||
SetBody(&common.Form{
|
||||
Expire: c.Expire,
|
||||
Description: c.Description,
|
||||
Notify: c.Notify,
|
||||
}).
|
||||
Post(rq.Url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := HandleResponse(c, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RespondExtended(w, resp)
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package lib
|
||||
|
||||
import (
|
||||
//"github.com/alecthomas/repr"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
"codeberg.org/scip/ephemerup/upctl/cfg"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const endpoint string = "http://localhost:8080/v1"
|
||||
|
||||
type Unit struct {
|
||||
name string
|
||||
apikey string // set to something else than "token" to fail auth
|
||||
wantfail bool // true: expect to fail
|
||||
files []string // path relative to ./t/
|
||||
expect string // regex used to parse the output
|
||||
|
||||
sendcode int // for httpmock
|
||||
sendjson string // struct to respond with
|
||||
sendfile string // bare file content to be sent
|
||||
route string // dito
|
||||
method string // method to use
|
||||
}
|
||||
|
||||
// simulate our ephemerup server
|
||||
func Intercept(tt Unit) {
|
||||
httpmock.RegisterResponder(tt.method, endpoint+tt.route,
|
||||
func(request *http.Request) (*http.Response, error) {
|
||||
var resp *http.Response
|
||||
|
||||
if tt.sendfile != "" {
|
||||
// simulate a file download
|
||||
content, err := ioutil.ReadFile(tt.sendfile)
|
||||
if err != nil {
|
||||
panic(err) // should not happen
|
||||
}
|
||||
|
||||
stat, err := os.Stat(tt.sendfile)
|
||||
if err != nil {
|
||||
panic(err) // should not happen as well
|
||||
}
|
||||
|
||||
resp = httpmock.NewStringResponse(tt.sendcode, string(content))
|
||||
resp.Header.Set("Content-Type", "text/markdown; charset=utf-8")
|
||||
resp.Header.Set("Content-Length", strconv.Itoa(int(stat.Size())))
|
||||
resp.Header.Set("Content-Disposition", "attachment; filename='t1'")
|
||||
} else {
|
||||
// simulate JSON response
|
||||
resp = httpmock.NewStringResponse(tt.sendcode, tt.sendjson)
|
||||
resp.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
})
|
||||
}
|
||||
|
||||
// execute the actual test
|
||||
func Check(t *testing.T, tt Unit, w *bytes.Buffer, err error) {
|
||||
testname := fmt.Sprintf("%s-%t", tt.name, tt.wantfail)
|
||||
|
||||
if err != nil && !tt.wantfail {
|
||||
t.Errorf("%s failed! wantfail: %t, error: %s", testname, tt.wantfail, err.Error())
|
||||
}
|
||||
|
||||
if tt.expect != "" {
|
||||
got := strings.TrimSpace(w.String())
|
||||
r := regexp.MustCompile(tt.expect)
|
||||
if !r.MatchString(got) {
|
||||
t.Errorf("%s failed! error: output does not match!\nexpect: %s\ngot:\n%s", testname, tt.expect, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadFiles(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
Apikey: "token",
|
||||
Endpoint: endpoint,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "upload-file",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: `{"success": true}`,
|
||||
files: []string{"../t/t1"}, // pwd is lib/ !
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
name: "upload-dir",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: `{"success": true}`,
|
||||
files: []string{"../t"}, // pwd is lib/ !
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
name: "upload-catch-nonexistent-file",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: `{"success": false}`,
|
||||
files: []string{"../t/none"},
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
name: "upload-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads",
|
||||
sendcode: 403,
|
||||
sendjson: `{"success": false}`,
|
||||
files: []string{"../t/t1"},
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
name: "upload-check-output",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: `{"uploads":[
|
||||
{
|
||||
"id":"cc2c965a","expire":"asap","file":"t1","members":["t1"],
|
||||
"uploaded":1679396814.890502,"context":"foo","url":""
|
||||
}
|
||||
],
|
||||
"success":true,
|
||||
"message":"Download url: http://localhost:8080/download/cc2c965a/t1",
|
||||
"code":200}`,
|
||||
files: []string{"../t/t1"}, // pwd is lib/ !
|
||||
method: "POST",
|
||||
expect: "Expire: On first access",
|
||||
},
|
||||
}
|
||||
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, UploadFiles(&w, conf, unit.files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
Apikey: "token",
|
||||
Endpoint: endpoint,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
listing := `{"uploads":[
|
||||
{
|
||||
"id":"cc2c965a","expire":"asap","file":"t1","members":["t1"],
|
||||
"uploaded":1679396814.890502,"context":"foo","url":""
|
||||
}
|
||||
],
|
||||
"success":true,
|
||||
"message":"",
|
||||
"code":200}`
|
||||
|
||||
listingnoaccess := `{"success":false,"message":"invalid context","code":503}`
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "list",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads",
|
||||
sendcode: 200,
|
||||
sendjson: listing,
|
||||
files: []string{},
|
||||
method: "GET",
|
||||
expect: `cc2c965a\s*asap\s*foo\s*2023-03-21`, // expect tabular output
|
||||
},
|
||||
{
|
||||
name: "list-catch-empty-json",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads",
|
||||
sendcode: 404,
|
||||
sendjson: "",
|
||||
files: []string{},
|
||||
method: "GET",
|
||||
},
|
||||
{
|
||||
name: "list-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads",
|
||||
sendcode: 503,
|
||||
sendjson: listingnoaccess,
|
||||
files: []string{},
|
||||
method: "GET",
|
||||
},
|
||||
}
|
||||
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, List(&w, conf, []string{}, common.TypeUpload))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribe(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
Apikey: "token",
|
||||
Endpoint: endpoint,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
listing := `{"uploads":[
|
||||
{
|
||||
"id":"cc2c965a","expire":"asap","file":"t1","members":["t1"],
|
||||
"uploaded":1679396814.890502,"context":"foo","url":""
|
||||
}
|
||||
],
|
||||
"success":true,
|
||||
"message":"",
|
||||
"code":200}`
|
||||
|
||||
listingnoaccess := `{"success":false,"message":"invalid context","code":503}`
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "describe",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendjson: listing,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
expect: `Created: 2023-03-21`,
|
||||
},
|
||||
{
|
||||
name: "describe-catch-empty-json",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendjson: "",
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
},
|
||||
{
|
||||
name: "describe-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 503,
|
||||
sendjson: listingnoaccess,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
},
|
||||
}
|
||||
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
unit.route += unit.files[0]
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, Describe(&w, conf, unit.files, common.TypeUpload))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
Apikey: "token",
|
||||
Endpoint: endpoint,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
listingnoaccess := `{"success":false,"message":"invalid context","code":503}`
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "delete",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendjson: `{"success":true,"message":"","code":200}`,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "DELETE",
|
||||
expect: `Upload cc2c965a successfully deleted`,
|
||||
},
|
||||
{
|
||||
name: "delete-catch-empty-json",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendjson: "",
|
||||
files: []string{"cc2c965a"},
|
||||
method: "DELETE",
|
||||
},
|
||||
{
|
||||
name: "delete-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 503,
|
||||
sendjson: listingnoaccess,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "DELETE",
|
||||
},
|
||||
}
|
||||
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
unit.route += unit.files[0]
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, Delete(&w, conf, unit.files, common.TypeUpload))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownload(t *testing.T) {
|
||||
conf := &cfg.Config{
|
||||
Mock: true,
|
||||
Apikey: "token",
|
||||
Endpoint: endpoint,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
listingnoaccess := `{"success":false,"message":"invalid context","code":503}`
|
||||
|
||||
tests := []Unit{
|
||||
{
|
||||
name: "download",
|
||||
apikey: "token",
|
||||
wantfail: false,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
sendfile: "../t/t1",
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
expect: `cc2c965a successfully downloaded to file t1`,
|
||||
},
|
||||
{
|
||||
name: "download-catch-empty-response",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 200,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
},
|
||||
{
|
||||
name: "download-catch-no-access",
|
||||
apikey: "token",
|
||||
wantfail: true,
|
||||
route: "/uploads/",
|
||||
sendcode: 503,
|
||||
sendjson: listingnoaccess,
|
||||
files: []string{"cc2c965a"},
|
||||
method: "GET",
|
||||
},
|
||||
}
|
||||
|
||||
for _, unit := range tests {
|
||||
var w bytes.Buffer
|
||||
unit.route += unit.files[0] + "/file"
|
||||
Intercept(unit)
|
||||
Check(t, unit, &w, Download(&w, conf, unit.files))
|
||||
|
||||
if unit.sendfile != "" {
|
||||
file := filepath.Base(unit.sendfile)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
os.Remove(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeberg.org/scip/ephemerup/common"
|
||||
req "github.com/imroc/req/v3"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
// make a human readable version of the expire setting
|
||||
func prepareExpire(expire string, start common.Timestamp) string {
|
||||
switch expire {
|
||||
case "asap":
|
||||
return "On first access"
|
||||
default:
|
||||
return time.Unix(start.Unix()+int64(common.Duration2int(expire)), 0).
|
||||
Format("2006-01-02 15:04:05")
|
||||
}
|
||||
}
|
||||
|
||||
// generic table writer
|
||||
func WriteTable(w io.Writer, headers []string, data [][]string) {
|
||||
tableString := &strings.Builder{}
|
||||
table := tablewriter.NewWriter(tableString)
|
||||
|
||||
table.SetHeader(headers)
|
||||
table.AppendBulk(data)
|
||||
|
||||
table.SetAutoWrapText(false)
|
||||
table.SetAutoFormatHeaders(true)
|
||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetCenterSeparator("")
|
||||
table.SetColumnSeparator("")
|
||||
table.SetRowSeparator("")
|
||||
table.SetHeaderLine(false)
|
||||
table.SetBorder(false)
|
||||
table.SetTablePadding("\t")
|
||||
table.SetNoWhiteSpace(true)
|
||||
|
||||
table.Render()
|
||||
|
||||
fmt.Fprintln(w, tableString.String())
|
||||
}
|
||||
|
||||
/*
|
||||
Print output like psql \x
|
||||
|
||||
Prints all Uploads and Forms which exist in common.Response,
|
||||
however, we expect only one kind of them to be actually filled, so
|
||||
the function can be used for forms and uploads.
|
||||
*/
|
||||
func WriteExtended(w io.Writer, response *common.Response) {
|
||||
format := fmt.Sprintf("%%%ds: %%s\n", Maxwidth)
|
||||
|
||||
// we shall only have 1 element, however, if we ever support more, here we go
|
||||
for _, entry := range response.Uploads {
|
||||
expire := prepareExpire(entry.Expire, entry.Created)
|
||||
fmt.Fprintf(w, format, "Upload-Id", entry.Id)
|
||||
fmt.Fprintf(w, format, "Description", entry.Description)
|
||||
fmt.Fprintf(w, format, "Expire", expire)
|
||||
fmt.Fprintf(w, format, "Context", entry.Context)
|
||||
fmt.Fprintf(w, format, "Created", entry.Created)
|
||||
fmt.Fprintf(w, format, "Filename", entry.File)
|
||||
fmt.Fprintf(w, format, "Url", entry.Url)
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
for _, entry := range response.Forms {
|
||||
expire := prepareExpire(entry.Expire, entry.Created)
|
||||
fmt.Fprintf(w, format, "Form-Id", entry.Id)
|
||||
fmt.Fprintf(w, format, "Description", entry.Description)
|
||||
fmt.Fprintf(w, format, "Expire", expire)
|
||||
fmt.Fprintf(w, format, "Context", entry.Context)
|
||||
fmt.Fprintf(w, format, "Created", entry.Created)
|
||||
fmt.Fprintf(w, format, "Notify", entry.Notify)
|
||||
fmt.Fprintf(w, format, "Url", entry.Url)
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
}
|
||||
|
||||
// extract an common.Response{} struct from json response
|
||||
func GetResponse(resp *req.Response) (*common.Response, error) {
|
||||
response := common.Response{}
|
||||
|
||||
if err := json.Unmarshal([]byte(resp.String()), &response); err != nil {
|
||||
return nil, errors.New("Could not unmarshall JSON response: " + err.Error())
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return nil, errors.New(response.Message)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// turn the Uploads{} struct into a table and print it
|
||||
func UploadsRespondTable(w io.Writer, resp *req.Response) error {
|
||||
response, err := GetResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response.Message != "" {
|
||||
fmt.Fprintln(w, response.Message)
|
||||
}
|
||||
|
||||
sort.SliceStable(response.Uploads, func(i, j int) bool {
|
||||
return response.Uploads[i].Created.Time.Unix() < response.Uploads[j].Created.Time.Unix()
|
||||
})
|
||||
|
||||
// tablewriter
|
||||
data := [][]string{}
|
||||
for _, entry := range response.Uploads {
|
||||
data = append(data, []string{
|
||||
entry.Id, entry.Description, entry.Expire, entry.Context,
|
||||
entry.Created.Format("2006-01-02 15:04:05"), entry.File,
|
||||
})
|
||||
}
|
||||
|
||||
WriteTable(w, []string{"UPLOAD-ID", "DESCRIPTION", "EXPIRE", "CONTEXT", "CREATED", "FILE"}, data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// turn the Forms{} struct into a table and print it
|
||||
func FormsRespondTable(w io.Writer, resp *req.Response) error {
|
||||
response, err := GetResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response.Message != "" {
|
||||
fmt.Fprintln(w, response.Message)
|
||||
}
|
||||
|
||||
sort.SliceStable(response.Forms, func(i, j int) bool {
|
||||
return response.Forms[i].Created.Time.Unix() < response.Forms[j].Created.Time.Unix()
|
||||
})
|
||||
|
||||
// tablewriter
|
||||
data := [][]string{}
|
||||
for _, entry := range response.Forms {
|
||||
data = append(data, []string{
|
||||
entry.Id, entry.Description, entry.Expire, entry.Context,
|
||||
entry.Created.Format("2006-01-02 15:04:05"), entry.Notify,
|
||||
})
|
||||
}
|
||||
|
||||
WriteTable(w, []string{"FORM-ID", "DESCRIPTION", "EXPIRE", "CONTEXT", "CREATED", "NOTIFY"}, data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// turn the Uploads{} struct into xtnd output and print it
|
||||
func RespondExtended(w io.Writer, resp *req.Response) error {
|
||||
response, err := GetResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response.Message != "" {
|
||||
fmt.Fprintln(w, response.Message)
|
||||
}
|
||||
|
||||
WriteExtended(w, response)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
Copyright © 2023 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"codeberg.org/scip/ephemerup/upctl/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
endpoint = "http://localhost:8080/api/v1"
|
||||
apikey = "0fddbff5d8010f81cd28a7d77f3e38981b13d6164c2fd6e1c3f60a4287630c37"
|
||||
@@ -1 +0,0 @@
|
||||
Wed Mar 29 03:01:21 PM CEST 2023
|
||||
BIN
upctl/upctl
BIN
upctl/upctl
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
endpoint = "http://localhost:8080/v1"
|
||||
apikey = "970b391f22f515d96b3e9b86a2c62c627968828e47b356994d2e583188b4190a"
|
||||
notify = "root@localhost"
|
||||
Reference in New Issue
Block a user