diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index e914bfc..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[bug-report]" -labels: bug -assignees: TLINDEN - ---- - -**Describtion** - - - -**Steps To Reproduce** - - - -**Expected behavior** - - - -**Version information** - - - -**Additional informations** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 8722b18..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Feature request -about: Suggest a feature -title: "[feature-request]" -labels: feature-request -assignees: TLINDEN - ---- - -**Describtion** - - - - -**Version information** - - diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 07ea1ef..0000000 --- a/.goreleaser.yaml +++ /dev/null @@ -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 }}) diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml deleted file mode 100644 index a1f43da..0000000 --- a/.woodpecker/build.yaml +++ /dev/null @@ -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] diff --git a/.woodpecker/image.yaml b/.woodpecker/image.yaml deleted file mode 100644 index 0dc3ec2..0000000 --- a/.woodpecker/image.yaml +++ /dev/null @@ -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 diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml deleted file mode 100644 index 916c008..0000000 --- a/.woodpecker/release.yaml +++ /dev/null @@ -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 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index e35e235..0000000 --- a/Dockerfile +++ /dev/null @@ -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 " - -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"] diff --git a/Makefile b/Makefile deleted file mode 100644 index 7a605e7..0000000 --- a/Makefile +++ /dev/null @@ -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 . - - -# -# 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 diff --git a/Makefile.dist b/Makefile.dist deleted file mode 100644 index 55d2f38..0000000 --- a/Makefile.dist +++ /dev/null @@ -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/ diff --git a/README.md b/README.md index 431f1c1..8ed6252 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,11 @@ [![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://codeberg.org/scip/ephemerup/raw/branch/main/LICENSE) [![Go Report Card](https://goreportcard.com/badge/codeberg.org/scip/ephemerup)](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 diff --git a/api/auth.go b/api/auth.go deleted file mode 100644 index 15734af..0000000 --- a/api/auth.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/api/cleaner.go b/api/cleaner.go deleted file mode 100644 index 7cc722b..0000000 --- a/api/cleaner.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/api/db.go b/api/db.go deleted file mode 100644 index 8ad7ee5..0000000 --- a/api/db.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/api/db_test.go b/api/db_test.go deleted file mode 100644 index 9ba1da5..0000000 --- a/api/db_test.go +++ /dev/null @@ -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 . -*/ -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") - } - } - }) - } -} diff --git a/api/fileio.go b/api/fileio.go deleted file mode 100644 index b289a50..0000000 --- a/api/fileio.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/api/form_handlers.go b/api/form_handlers.go deleted file mode 100644 index 44a4ea0..0000000 --- a/api/form_handlers.go +++ /dev/null @@ -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 . -*/ -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) -} diff --git a/api/mail.go b/api/mail.go deleted file mode 100644 index 28cf70c..0000000 --- a/api/mail.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/api/server.go b/api/server.go deleted file mode 100644 index 6b3513e..0000000 --- a/api/server.go +++ /dev/null @@ -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 . -*/ -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, - }) -} diff --git a/api/status_handlers.go b/api/status_handlers.go deleted file mode 100644 index 500566e..0000000 --- a/api/status_handlers.go +++ /dev/null @@ -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 . -*/ -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) -} diff --git a/api/upload_handlers.go b/api/upload_handlers.go deleted file mode 100644 index d18ceea..0000000 --- a/api/upload_handlers.go +++ /dev/null @@ -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 . -*/ -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) -} diff --git a/api/utils.go b/api/utils.go deleted file mode 100644 index 9e9c805..0000000 --- a/api/utils.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/cfg/config.go b/cfg/config.go deleted file mode 100644 index 02100be..0000000 --- a/cfg/config.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/charts/cm.yaml b/charts/cm.yaml deleted file mode 100644 index 3d62ae0..0000000 --- a/charts/cm.yaml +++ /dev/null @@ -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 = [ - ] diff --git a/charts/ephemerup/.helmignore b/charts/ephemerup/.helmignore deleted file mode 100644 index f0c1319..0000000 --- a/charts/ephemerup/.helmignore +++ /dev/null @@ -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 diff --git a/charts/ephemerup/Chart.yaml b/charts/ephemerup/Chart.yaml deleted file mode 100644 index 5babdca..0000000 --- a/charts/ephemerup/Chart.yaml +++ /dev/null @@ -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: "" diff --git a/charts/ephemerup/README.md b/charts/ephemerup/README.md deleted file mode 100644 index 6de60e2..0000000 --- a/charts/ephemerup/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# ephemerup - -![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.0.2](https://img.shields.io/badge/AppVersion-0.0.2-informational?style=flat-square) - -A Helm chart for Ephemerup. - -## Source Code - -* - -## 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) diff --git a/charts/ephemerup/charts/common-1.16.0.tgz b/charts/ephemerup/charts/common-1.16.0.tgz deleted file mode 100644 index aae0af3..0000000 Binary files a/charts/ephemerup/charts/common-1.16.0.tgz and /dev/null differ diff --git a/charts/ephemerup/templates/NOTES.txt b/charts/ephemerup/templates/NOTES.txt deleted file mode 100644 index 2f66bb5..0000000 --- a/charts/ephemerup/templates/NOTES.txt +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/charts/ephemerup/templates/_helpers.tpl b/charts/ephemerup/templates/_helpers.tpl deleted file mode 100644 index 7d2ec6f..0000000 --- a/charts/ephemerup/templates/_helpers.tpl +++ /dev/null @@ -1,8 +0,0 @@ -{{/* - Return the proper image name -*/}} -{{- define "ephemerup.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} -{{- end -}} - - diff --git a/charts/ephemerup/templates/configmap.yaml b/charts/ephemerup/templates/configmap.yaml deleted file mode 100644 index aced5a6..0000000 --- a/charts/ephemerup/templates/configmap.yaml +++ /dev/null @@ -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 }} diff --git a/charts/ephemerup/templates/ingress.yaml b/charts/ephemerup/templates/ingress.yaml deleted file mode 100644 index 6164d19..0000000 --- a/charts/ephemerup/templates/ingress.yaml +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/charts/ephemerup/templates/secrets.yaml b/charts/ephemerup/templates/secrets.yaml deleted file mode 100644 index c95b3a8..0000000 --- a/charts/ephemerup/templates/secrets.yaml +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/charts/ephemerup/templates/service-monitor.yaml b/charts/ephemerup/templates/service-monitor.yaml deleted file mode 100644 index 293112c..0000000 --- a/charts/ephemerup/templates/service-monitor.yaml +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/charts/ephemerup/templates/service.yaml b/charts/ephemerup/templates/service.yaml deleted file mode 100644 index 2ae2000..0000000 --- a/charts/ephemerup/templates/service.yaml +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/charts/ephemerup/templates/statefulset.yaml b/charts/ephemerup/templates/statefulset.yaml deleted file mode 100644 index ce6bd2a..0000000 --- a/charts/ephemerup/templates/statefulset.yaml +++ /dev/null @@ -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 }} diff --git a/charts/ephemerup/values.schema.json b/charts/ephemerup/values.schema.json deleted file mode 100644 index 545568d..0000000 --- a/charts/ephemerup/values.schema.json +++ /dev/null @@ -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"] -} \ No newline at end of file diff --git a/charts/ephemerup/values.yaml b/charts/ephemerup/values.yaml deleted file mode 100644 index c235079..0000000 --- a/charts/ephemerup/values.yaml +++ /dev/null @@ -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 diff --git a/cmd/formtemplate.go b/cmd/formtemplate.go deleted file mode 100644 index 40e583b..0000000 --- a/cmd/formtemplate.go +++ /dev/null @@ -1,106 +0,0 @@ -package cmd - -const formtemplate = ` - - - - - - - - - - File upload form - - - - - -
-

Upload form {{ .Id }}

- -
- - -
-
-
-

- Use this form to upload one or more files. The creator of the form will automatically get notified. -

-
-
- - -
-
- -
- -
-
- -
- -
- -
-
-
- - -
-
-
- - - - - - - -` diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index a16c372..0000000 --- a/cmd/root.go +++ /dev/null @@ -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 . -*/ -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)=":" - -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 -} diff --git a/common/common_test.go b/common/common_test.go deleted file mode 100644 index 0f22a4d..0000000 --- a/common/common_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/common/go.mod b/common/go.mod deleted file mode 100644 index a136996..0000000 --- a/common/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module codeberg.org/scip/ephemerup/common - -go 1.18 diff --git a/common/timestamp.go b/common/timestamp.go deleted file mode 100644 index 4419b0f..0000000 --- a/common/timestamp.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/common/types.go b/common/types.go deleted file mode 100644 index e762374..0000000 --- a/common/types.go +++ /dev/null @@ -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 . -*/ -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 - } -} diff --git a/common/utils.go b/common/utils.go deleted file mode 100644 index 76d2b97..0000000 --- a/common/utils.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/demo/upctl.gif b/demo/upctl.gif deleted file mode 100644 index a118170..0000000 Binary files a/demo/upctl.gif and /dev/null differ diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index a2f52a9..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: "3.9" -services: - ephemerup: - build: . - ports: - - "8080:8080" diff --git a/ephemerup.hcl b/ephemerup.hcl deleted file mode 100644 index 69be05c..0000000 --- a/ephemerup.hcl +++ /dev/null @@ -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 = "" -} diff --git a/go.mod b/go.mod deleted file mode 100644 index b2dbfa8..0000000 --- a/go.mod +++ /dev/null @@ -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 diff --git a/go.sum b/go.sum deleted file mode 100644 index fe3c18d..0000000 --- a/go.sum +++ /dev/null @@ -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= diff --git a/main.go b/main.go deleted file mode 100644 index 91a5a64..0000000 --- a/main.go +++ /dev/null @@ -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 . -*/ -package main - -import ( - "log" - - "codeberg.org/scip/ephemerup/cmd" -) - -func main() { - err := cmd.Execute() - if err != nil { - log.Fatal(err.Error()) - } -} diff --git a/mkrel.sh b/mkrel.sh deleted file mode 100755 index 119d678..0000000 --- a/mkrel.sh +++ /dev/null @@ -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 . - - -# 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 " - 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 - diff --git a/nokeys.hcl b/nokeys.hcl deleted file mode 100644 index df89c42..0000000 --- a/nokeys.hcl +++ /dev/null @@ -1,3 +0,0 @@ -# -*-ruby-*- -listen = ":8080" -bodylimit = 10001 diff --git a/templates/formtemplate.html b/templates/formtemplate.html deleted file mode 100644 index c67318a..0000000 --- a/templates/formtemplate.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - File upload form - - - - - -
-

Upload form {{ .Id }}

- -
- - -
-
-
-

- Use this form to upload one or more files. The creator of the form will automatically get notified. -

-
-
- - -
-
- -
- -
-
- -
- -
- -
-
-
- - -
-
-
- - - - - - - diff --git a/upctl/Makefile b/upctl/Makefile deleted file mode 100644 index 15656c6..0000000 --- a/upctl/Makefile +++ /dev/null @@ -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 . - - -# -# 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 ./... diff --git a/upctl/README.md b/upctl/README.md deleted file mode 100644 index 575b98c..0000000 --- a/upctl/README.md +++ /dev/null @@ -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 -``` diff --git a/upctl/cfg/config.go b/upctl/cfg/config.go deleted file mode 100644 index f50c771..0000000 --- a/upctl/cfg/config.go +++ /dev/null @@ -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 . -*/ -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) -} diff --git a/upctl/cmd/formcommands.go b/upctl/cmd/formcommands.go deleted file mode 100644 index d4529f1..0000000 --- a/upctl/cmd/formcommands.go +++ /dev/null @@ -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 . -*/ -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] ", - 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] ", - 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 -} diff --git a/upctl/cmd/maincommands.go b/upctl/cmd/maincommands.go deleted file mode 100644 index ab1aadf..0000000 --- a/upctl/cmd/maincommands.go +++ /dev/null @@ -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 . -*/ -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] ", - 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] ", - 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 -} diff --git a/upctl/cmd/root.go b/upctl/cmd/root.go deleted file mode 100644 index af93c9f..0000000 --- a/upctl/cmd/root.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/upctl/go.mod b/upctl/go.mod deleted file mode 100644 index bf58b4b..0000000 --- a/upctl/go.mod +++ /dev/null @@ -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 diff --git a/upctl/go.sum b/upctl/go.sum deleted file mode 100644 index 8820d6d..0000000 --- a/upctl/go.sum +++ /dev/null @@ -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= diff --git a/upctl/lib/client.go b/upctl/lib/client.go deleted file mode 100644 index d14cc16..0000000 --- a/upctl/lib/client.go +++ /dev/null @@ -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 . -*/ -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) -} diff --git a/upctl/lib/client_test.go b/upctl/lib/client_test.go deleted file mode 100644 index e0cd355..0000000 --- a/upctl/lib/client_test.go +++ /dev/null @@ -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 . -*/ -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) - } - } - } -} diff --git a/upctl/lib/output.go b/upctl/lib/output.go deleted file mode 100644 index 13eb135..0000000 --- a/upctl/lib/output.go +++ /dev/null @@ -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 . -*/ -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 -} diff --git a/upctl/main.go b/upctl/main.go deleted file mode 100644 index dc2c091..0000000 --- a/upctl/main.go +++ /dev/null @@ -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 . -*/ -package main - -import ( - "codeberg.org/scip/ephemerup/upctl/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/upctl/super.hcl b/upctl/super.hcl deleted file mode 100644 index a8f0e45..0000000 --- a/upctl/super.hcl +++ /dev/null @@ -1,2 +0,0 @@ -endpoint = "http://localhost:8080/api/v1" -apikey = "0fddbff5d8010f81cd28a7d77f3e38981b13d6164c2fd6e1c3f60a4287630c37" diff --git a/upctl/t/t1 b/upctl/t/t1 deleted file mode 100644 index d87562a..0000000 --- a/upctl/t/t1 +++ /dev/null @@ -1 +0,0 @@ -Wed Mar 29 03:01:21 PM CEST 2023 diff --git a/upctl/upctl b/upctl/upctl deleted file mode 100755 index 0bca443..0000000 Binary files a/upctl/upctl and /dev/null differ diff --git a/upctl/upctl.hcl b/upctl/upctl.hcl deleted file mode 100644 index 4031105..0000000 --- a/upctl/upctl.hcl +++ /dev/null @@ -1,3 +0,0 @@ -endpoint = "http://localhost:8080/v1" -apikey = "970b391f22f515d96b3e9b86a2c62c627968828e47b356994d2e583188b4190a" -notify = "root@localhost"