Initial commit

This commit is contained in:
Tyler 2017-06-11 02:14:13 -04:00
commit 0511a71fea
26 changed files with 1553 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/deb-simple
/repo
/src
conf.json
*.pprof

16
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,16 @@
image: knightswarm/gobuild:1.6
before_script:
- go get github.com/tools/godep
- export GOPATH=$(pwd)
stages:
- build
compile:
stage: build
script:
- export DEB_SIMPLE_VERSION=`grep "var VERSION" main.go | awk -F\" '{print $2}'`
- godep restore
- chmod +x ./ci/package.sh
- ./ci/package.sh

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
language: go
go:
- 1.5
before_install:
- go get github.com/blakesmith/ar
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script:
- $HOME/gopath/bin/goveralls -service=travis-ci
- go test -run=XXX -bench=.

21
LICENSE.txt Normal file
View File

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

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
all:
go build -o deb-simple .
install:
mkdir -p $(DESTDIR)/usr/bin
cp deb-simple $(DESTDIR)/usr/bin
chmod 755 $(DESTDIR)/usr/bin/deb-simple
clean:
rm -f deb-simple

52
README.md Normal file
View File

@ -0,0 +1,52 @@
[![Build Status](https://travis-ci.org/esell/deb-simple.svg?branch=master)](https://travis-ci.org/esell/deb-simple)
[![Coverage Status](https://coveralls.io/repos/github/esell/deb-simple/badge.svg?branch=master)](https://coveralls.io/github/esell/deb-simple?branch=master)
# deb-simple (get it? dead simple.. deb simple...)
A lightweight, bare-bones apt repository server.
# Purpose
This project came from a need I had to be able to serve up already created deb packages without a lot of fuss. Most of the existing solutions
I found were either geared at mirroring existing "official" repos or for providing your packages to the public. My need was just something that
I could use internally to install already built deb packages via apt-get. I didn't care about change files, signed packages, etc. Since this was
to be used in a CI pipeline it had to support remote uploads and be able to update the package list after each upload.
# What it does:
- Supports multiple versions of packages
- Supports multi-arch repos (i386, amd64, custom, etc)
- Supports uploading via HTTP/HTTPS POST requests
- Supports removing packages via HTTP/HTTPS DELETE requests
- Does NOT require a changes file
- Supports uploads from various locations without corrupting the repo
# What it doesn't do:
- Create actual packages
- Mirror existing repos
# Usage:
Install using `go get`. Fill out the conf.json file with the values you want, it should be pretty self-explanatory, then fire it up!
Once it is running POST a file to the `/upload` endpoint:
`curl -XPOST 'http://localhost:9090/upload?arch=amd64&distro=stable' -F "file=@myapp.deb"`
Or delete an existing file:
`curl -XDELETE 'http://localhost:9090/delete' -d '{"filename":"myapp.deb","distroName":"stable","arch":"amd64"}'`
To use your new repo you will have to add a line like this to your sources.list file:
`deb http://my-hostname:listenPort/ stable main`
`my-hostname` should be the actual hostname/IP where you are running deb-simple and `listenPort` will be whatever you set in the config. By default deb-simple puts everything into the `stable` distro and `main` section. If you have enabled SSL you will want to swap `http` for `https`.
#License:
[MIT](LICENSE.txt) so go crazy. Would appreciate PRs for anything cool you add though :)

306
apt.go Normal file
View File

@ -0,0 +1,306 @@
package main
import (
"os"
"fmt"
"github.com/blakesmith/ar"
"bytes"
"io"
"compress/gzip"
"archive/tar"
"log"
"path/filepath"
"io/ioutil"
"strings"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"golang.org/x/crypto/openpgp"
"bufio"
"time"
)
func inspectPackage(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", fmt.Errorf("error opening package file %s: %s", filename, err)
}
arReader := ar.NewReader(f)
defer f.Close()
var controlBuf bytes.Buffer
for {
header, err := arReader.Next()
if err == io.EOF {
break
}
if err != nil {
return "", fmt.Errorf("error in inspectPackage loop: %s", err)
}
if strings.Trim(header.Name, "/") == "control.tar.gz" {
io.Copy(&controlBuf, arReader)
return inspectPackageControl(controlBuf)
}
}
return "", nil
}
func inspectPackageControl(filename bytes.Buffer) (string, error) {
gzf, err := gzip.NewReader(bytes.NewReader(filename.Bytes()))
if err != nil {
return "", fmt.Errorf("error creating gzip reader: %s", err)
}
tarReader := tar.NewReader(gzf)
var controlBuf bytes.Buffer
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return "", fmt.Errorf("failed to inspect package: %s", err)
}
name := header.Name
switch header.Typeflag {
case tar.TypeDir:
continue
case tar.TypeReg:
if name == "./control" {
io.Copy(&controlBuf, tarReader)
return controlBuf.String(), nil
}
default:
log.Printf(
"Unable to figure out type : %c in file %s\n",
header.Typeflag, name,
)
}
}
return "", nil
}
func createPackagesGz(config Conf, distro, arch string) error {
stdFile, err := os.Create(filepath.Join(config.ArchPath(distro, arch), "Packages"))
if err != nil {
return fmt.Errorf("failed to create packages: %s", err)
}
defer stdFile.Close()
gzipFile, err := os.Create(filepath.Join(config.ArchPath(distro, arch), "Packages.gz"))
if err != nil {
return fmt.Errorf("failed to create packages.gz: %s", err)
}
defer gzipFile.Close()
gzWriter := gzip.NewWriter(gzipFile)
defer gzWriter.Close()
stdOut := bufio.NewWriter(stdFile)
// loop through each directory
// run inspectPackage
poolPath := config.PoolPath(distro, arch)
dirList, err := ioutil.ReadDir(poolPath)
if err != nil {
return fmt.Errorf("scanning: %s: %s", poolPath, err)
}
for _, firstChar := range dirList {
// Recursing isn't fun...
dirList, err := ioutil.ReadDir(filepath.Join(poolPath, firstChar.Name()))
if err != nil {
return fmt.Errorf("scanning: %s: %s", poolPath, err)
}
for _, p := range dirList {
dirList, err := ioutil.ReadDir(filepath.Join(poolPath, firstChar.Name(), p.Name()))
if err != nil {
return fmt.Errorf("scanning: %s: %s", poolPath, err)
}
for _, debFile := range dirList {
if strings.HasSuffix(debFile.Name(), "deb") {
var packBuf bytes.Buffer
debName := packageName(debFile.Name())
debPath := filepath.Join(config.PoolPackagePath(distro, arch, debFile.Name()), debFile.Name())
tempCtlData, err := inspectPackage(debPath)
if err != nil {
return err
}
packBuf.WriteString(tempCtlData)
var size int64 = debFile.Size()
if size == 0 {
if stat, err := os.Stat(debPath); err == nil {
size = stat.Size()
}
}
dir := filepath.Join("pool/main", distro, arch, debName[0:1], debName, debFile.Name())
log.Println("path:", dir)
fmt.Fprintf(&packBuf, "Filename: %s\n", strings.Replace(dir, "\\", "/", -1))
fmt.Fprintf(&packBuf, "Size: %d\n", size)
f, err := os.Open(debPath)
if err != nil {
log.Println("error opening deb file: ", err)
}
var (
md5hash = md5.New()
sha1hash = sha1.New()
sha256hash = sha256.New()
)
f.Seek(0, 0)
if _, err := io.Copy(md5hash, f); err != nil {
log.Println("error with the md5 hash: ", err)
}
fmt.Fprintf(&packBuf, "MD5sum: %s\n",
hex.EncodeToString(md5hash.Sum(nil)))
f.Seek(0, 0)
if _, err = io.Copy(sha1hash, f); err != nil {
log.Println("error with the sha1 hash: ", err)
}
fmt.Fprintf(&packBuf, "SHA1: %s\n",
hex.EncodeToString(sha1hash.Sum(nil)))
f.Seek(0, 0)
if _, err = io.Copy(sha256hash, f); err != nil {
log.Println("error with the sha256 hash: ", err)
}
fmt.Fprintf(&packBuf, "SHA256: %s\n",
hex.EncodeToString(sha256hash.Sum(nil)))
packBuf.WriteString("\n\n")
stdOut.Write(packBuf.Bytes())
gzWriter.Write(packBuf.Bytes())
f.Close()
f = nil
}
}
}
}
stdOut.Flush()
gzWriter.Flush()
return nil
}
func createRelease(config Conf, distro, arch string) error {
outfile, err := os.Create(filepath.Join(config.DistPath(distro), "Release"))
if err != nil {
return fmt.Errorf("failed to create Release: %s", err)
}
defer outfile.Close()
var packBuf bytes.Buffer
fmt.Fprintf(&packBuf, "Suite: %s\n", distro)
fmt.Fprintf(&packBuf, "Architectures: %s\n", arch)
fmt.Fprint(&packBuf, "Components: main\n")
fmt.Fprintf(&packBuf, "Date: %s\n", time.Now().In(time.UTC).Format("Mon, 02 Jan 2006 15:04:05 -0700"))
basePath := filepath.Join("main", "binary-" + arch)
dirList, err := ioutil.ReadDir(filepath.Join(config.DistPath(distro), "main", "binary-" + arch))
if err != nil {
return fmt.Errorf("scanning: %s: %s", config.PoolPath(distro, arch), err)
}
var md5Buf bytes.Buffer
var sha1Buf bytes.Buffer
var sha256Buf bytes.Buffer
for _, file := range dirList {
filePath := filepath.Join(config.DistPath(distro), basePath, file.Name())
log.Println("File path:", filePath)
fileLocalPath := filepath.Join(basePath, file.Name())
fileLocalPath = strings.Replace(fileLocalPath, "\\", "/", -1)
f, err := os.Open(filePath)
if err != nil {
log.Println("error opening source file: ", err)
}
var size int64 = file.Size()
if size == 0 {
if stat, err := os.Stat(filePath); err == nil {
size = stat.Size()
}
}
var (
md5hash = md5.New()
sha1hash = sha1.New()
sha256hash = sha256.New()
)
f.Seek(0, 0)
if _, err := io.Copy(md5hash, f); err != nil {
log.Println("error with the md5 hashing: ", err)
}
fmt.Fprintf(&md5Buf, " %s %d %s\n", hex.EncodeToString(md5hash.Sum(nil)), size, fileLocalPath)
f.Seek(0, 0)
if _, err := io.Copy(sha1hash, f); err != nil {
log.Println("error with the sha1 hashing: ", err)
}
fmt.Fprintf(&sha1Buf, " %s %d %s\n", hex.EncodeToString(sha1hash.Sum(nil)), size, fileLocalPath)
f.Seek(0, 0)
if _, err := io.Copy(sha256hash, f); err != nil {
log.Println("error with the sha256 hashing: ", err)
}
fmt.Fprintf(&sha256Buf, " %s %d %s\n", hex.EncodeToString(sha256hash.Sum(nil)), size, fileLocalPath)
f.Close()
f = nil
}
fmt.Fprintf(&packBuf, "MD5Sum:\n%s", string(md5Buf.Bytes()))
fmt.Fprintf(&packBuf, "SHA1:\n%s", string(sha1Buf.Bytes()))
fmt.Fprintf(&packBuf, "SHA256:\n%s", string(sha256Buf.Bytes()))
outfile.Write(packBuf.Bytes())
if pgpEntity != nil {
gpgfile, err := os.Create(filepath.Join(config.DistPath(distro), "Release.gpg"))
if err != nil {
return fmt.Errorf("failed to create Release.gpg: %s", err)
}
defer gpgfile.Close()
byteReader := bytes.NewReader(packBuf.Bytes())
if err := openpgp.ArmoredDetachSignText(gpgfile, pgpEntity, byteReader, nil); err != nil {
return fmt.Errorf("failed to sign Release: %s", err)
}
}
return nil
}

15
ci/package.sh Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
curl -s -X POST -F "file=@debian/changelog" -F "name=deb-simple" -F "version=$DEB_SIMPLE_VERSION" -F "author=Gitlab CI" -F "email=gitlab@knightswarm.com" -F "changes[]=Auto build for $CI_BUILD_REF" https://api.meow.tf/changelog/update > debian/changelog
if [ ! -z "$GITLAB_CI_PUT_URL" ]; then
curl -s -X PUT -F "file_path=debian/changelog" -F branch_name=master -F "commit_message=[ci skip] Update changelog for $CI_BUILD_REF" -F "content=<debian/changelog" $GITLABCI_PUT_URL
fi
chmod +x debian/rules
fakeroot debian/rules clean binary
if [ ! -z "$UPLOAD_URL" ]; then
FILE=`ls ./build | head -n 1`
curl -s -X POST "$UPLOAD_URL" -F "file=@build/$FILE"
fi

5
debian/changelog vendored Normal file
View File

@ -0,0 +1,5 @@
deb-simple (1.0.0) UNRELEASED; urgency=medium
* Initial build
-- Gitlab CI <gitlab@knightswarm.com> Fri, 25 Mar 2016 21:53:18 +0000

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

13
debian/control vendored Normal file
View File

@ -0,0 +1,13 @@
Source: deb-simple
Maintainer: Tyler Stuyfzand <tyler.stuyfzand@knightswarm.com>
Section: misc
Priority: optional
Standards-Version: 3.9.2
Build-Depends: debhelper (>= 9)
Package: deb-simple
Architecture: any
Depends: ${misc:Depends}
Description: Dead Simple Debian Repository
A debian repository server which supports file uploads
and package signing

0
debian/copyright vendored Normal file
View File

4
debian/deb-simple.dirs vendored Normal file
View File

@ -0,0 +1,4 @@
usr/bin
etc
opt/deb-simple
opt/deb-simple/repo

159
debian/deb-simple.init vendored Normal file
View File

@ -0,0 +1,159 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: deb-simple
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Debian Package Server
# Description: Dead Simple Debian Package Server written in Go
### END INIT INFO
# Author: Tyler Stuyfzand <tyler.stuyfzand@knightswarm.com>
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Simple Debian Package Server"
NAME=deb-simple
USER=deb-simple
DAEMON=/usr/bin/$NAME
DAEMON_ARGS="-c /etc/deb-simple.conf"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --chuid $USER:$USER --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --chuid $USER:$USER --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON -- \
$DAEMON_ARGS \
|| return 2
# Add code here, if necessary, that waits for the process to be ready
# to handle requests from services started subsequently which depend
# on this one. As a last resort, sleep for some time.
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
#
# If the daemon can reload its configuration without
# restarting (for example, when it is sent a SIGHUP),
# then implement that here.
#
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
return 0
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
#reload|force-reload)
#
# If do_reload() is not implemented then leave this commented out
# and leave 'force-reload' as an alias for 'restart'.
#
#log_daemon_msg "Reloading $DESC" "$NAME"
#do_reload
#log_end_msg $?
#;;
restart|force-reload)
#
# If the "reload" option is implemented then remove the
# 'force-reload' alias
#
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:

14
debian/postinst vendored Normal file
View File

@ -0,0 +1,14 @@
#!/bin/sh
set -e
if test "$1" = "configure"; then
if ! getent passwd deb-simple > /dev/null; then
adduser --quiet --system --group \
--home /opt/deb-simple \
--no-create-home \
deb-simple
fi
mkdir -p /opt/deb-simple/repo || true
chown -R deb-simple:deb-simple /opt/deb-simple
fi

13
debian/rules vendored Normal file
View File

@ -0,0 +1,13 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_auto_install:
dh_auto_install
install -d debian/deb-simple/opt/deb-simple
install -d debian/deb-simple/opt/deb-simple/repo
install -m 644 sample_conf.json debian/deb-simple/etc/deb-simple.conf
override_dh_builddeb:
mkdir ./build || true
dh_builddeb --destdir=./build --

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

16
glide.lock generated Normal file
View File

@ -0,0 +1,16 @@
hash: f49345e0dcd6cb4f3c49af64a95afbea2e608503cd6c1909d300e4a457ef65a6
updated: 2017-05-02T21:07:03.8312824-04:00
imports:
- name: github.com/blakesmith/ar
version: 8bd4349a67f2533b078dbc524689d15dba0f4659
- name: golang.org/x/crypto
version: d1464577745bc7f4e74f65be9cfbd09436a729d6
subpackages:
- cast5
- openpgp
- openpgp/armor
- openpgp/elgamal
- openpgp/errors
- openpgp/packet
- openpgp/s2k
testImports: []

6
glide.yaml Normal file
View File

@ -0,0 +1,6 @@
package: .
import:
- package: github.com/blakesmith/ar
- package: golang.org/x/crypto
subpackages:
- openpgp

129
http.go Normal file
View File

@ -0,0 +1,129 @@
package main
import (
"net/http"
"io"
"os"
"path/filepath"
"log"
"encoding/json"
"fmt"
)
func uploadHandler(config Conf) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "method not supported", http.StatusMethodNotAllowed)
return
}
archType := r.URL.Query().Get("arch")
if archType == "" {
archType = "all"
}
distroName := r.URL.Query().Get("distro")
if distroName == "" {
distroName = "stable"
}
key := r.URL.Query().Get("key")
if key == "" || key != config.Key {
http.Error(w, "unauthorized", 403)
return
}
reader, err := r.MultipartReader()
if err != nil {
httpErrorf(w, "error creating multipart reader: %s", err)
return
}
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
if part.FileName() == "" {
continue
}
newPath := config.PoolPackagePath(distroName, archType, part.FileName())
if _, err := os.Stat(newPath); err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(newPath, 0755); err != nil {
httpErrorf(w, "error creating path: %s", err)
return
}
}
}
dst, err := os.Create(filepath.Join(newPath, part.FileName()))
if err != nil {
httpErrorf(w, "error creating deb file: %s", err)
return
}
defer dst.Close()
if _, err := io.Copy(dst, part); err != nil {
httpErrorf(w, "error writing deb file: %s", err)
return
}
}
mutex.Lock()
defer mutex.Unlock()
log.Println("got lock, updating package list...")
if err := createPackagesGz(config, distroName, archType); err != nil {
httpErrorf(w, "error creating package: %s", err)
return
}
err = createRelease(config, distroName, archType)
if err != nil {
httpErrorf(w, "error creating package: %s", err)
return
}
w.WriteHeader(http.StatusOK)
})
}
func deleteHandler(config Conf) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
http.Error(w, "method not supported", http.StatusMethodNotAllowed)
return
}
var toDelete DeleteObj
if err := json.NewDecoder(r.Body).Decode(&toDelete); err != nil {
httpErrorf(w, "failed to decode json: %s", err)
return
}
debPath := filepath.Join(config.ArchPath(toDelete.DistroName, toDelete.Arch), toDelete.Filename)
if err := os.Remove(debPath); err != nil {
httpErrorf(w, "failed to delete: %s", err)
return
}
key := r.URL.Query().Get("key")
if key == "" || key != config.Key {
http.Error(w, "unauthorized", http.StatusForbidden)
return
}
mutex.Lock()
defer mutex.Unlock()
log.Println("got lock, updating package list...")
if err := createPackagesGz(config, toDelete.DistroName, toDelete.Arch); err != nil {
httpErrorf(w, "failed to delete package: %s", err)
return
}
if err := createRelease(config, toDelete.DistroName, toDelete.Arch); err != nil {
httpErrorf(w, "failed to delete package: %s", err)
return
}
})
}
func httpErrorf(w http.ResponseWriter, format string, a ...interface{}) {
err := fmt.Errorf(format, a...)
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}

177
main.go Normal file
View File

@ -0,0 +1,177 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"strings"
"golang.org/x/crypto/openpgp"
"runtime"
"errors"
)
var VERSION string = "1.1.0"
type Conf struct {
ListenPort string `json:"listenPort"`
RootRepoPath string `json:"rootRepoPath"`
SupportArch []string `json:"supportedArch"`
DistroNames []string `json:"distroNames"`
PGPSecretKey string `json:"pgpSecretKey"`
PGPPassphrase string `json:"pgpPassphrase"`
EnableSSL bool `json:"enableSSL"`
SSLCert string `json:"SSLcert"`
SSLKey string `json:"SSLkey"`
Key string `json:"key"`
}
func (c Conf) DistPath(distro string) string {
return filepath.Join(c.RootRepoPath, "dists", distro)
}
func (c Conf) ArchPath(distro, arch string) string {
return filepath.Join(c.RootRepoPath, "dists", distro, "main/binary-"+arch)
}
func (c Conf) PoolPath(distro, arch string) string {
return filepath.Join(c.RootRepoPath, "pool/main", distro, arch)
}
func (c Conf) PoolPackagePath(distro, arch, name string) string {
name = packageName(name)
return filepath.Join(c.RootRepoPath, "pool/main", distro, arch, name[0:1], name)
}
func packageName(name string) string {
if index := strings.Index(name, "_"); index != -1 {
name = name[:index]
}
return name
}
type DeleteObj struct {
Filename string
DistroName string
Arch string
}
var (
mutex sync.Mutex
configFile = flag.String("c", "conf.json", "config file location")
flagShowVersion = flag.Bool("version", false, "Show dnsconfig version")
parsedConfig = Conf{}
pgpEntity *openpgp.Entity
)
func main() {
flag.Parse()
if *flagShowVersion {
fmt.Printf("deb-simple %s (%s)\n", VERSION, runtime.Version())
os.Exit(0)
}
file, err := ioutil.ReadFile(*configFile)
if err != nil {
log.Fatalln("unable to read config file, exiting...")
}
if err := json.Unmarshal(file, &parsedConfig); err != nil {
log.Fatalln("unable to marshal config file, exiting...")
}
if err := createDirs(parsedConfig); err != nil {
log.Println(err)
log.Fatalln("error creating directory structure, exiting")
}
if err := setupPgp(parsedConfig); err != nil {
log.Println(err)
log.Fatalln("error loading pgp key, exiting")
}
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(parsedConfig.RootRepoPath))))
http.Handle("/upload", uploadHandler(parsedConfig))
http.Handle("/delete", deleteHandler(parsedConfig))
if parsedConfig.EnableSSL {
log.Println("running with SSL enabled")
log.Fatalln(http.ListenAndServeTLS(":"+parsedConfig.ListenPort, parsedConfig.SSLCert, parsedConfig.SSLKey, nil))
} else {
log.Println("running without SSL enabled")
log.Fatalln(http.ListenAndServe(":"+parsedConfig.ListenPort, nil))
}
}
func setupPgp(config Conf) error {
if config.PGPSecretKey == "" {
return nil
}
secretKey, err := os.Open(config.PGPSecretKey)
if err != nil {
return fmt.Errorf("failed to open private key ring file: %s", err)
}
defer secretKey.Close()
entitylist, err := openpgp.ReadKeyRing(secretKey)
if err != nil {
return fmt.Errorf("failed to read key ring: %s", err)
}
if len(entitylist) < 1 {
return errors.New("no keys in key ring")
}
pgpEntity = entitylist[0]
passphrase := []byte(config.PGPPassphrase)
if err := pgpEntity.PrivateKey.Decrypt(passphrase); err != nil {
return err
}
for _, subkey := range pgpEntity.Subkeys {
err := subkey.PrivateKey.Decrypt(passphrase)
if err != nil {
return err
}
}
return nil
}
func createDirs(config Conf) error {
for _, distro := range config.DistroNames {
for _, arch := range config.SupportArch {
if _, err := os.Stat(config.ArchPath(distro, arch)); err != nil {
if os.IsNotExist(err) {
log.Printf("Directory for %s (%s) does not exist, creating", distro, arch)
if err := os.MkdirAll(config.ArchPath(distro, arch), 0755); err != nil {
return fmt.Errorf("error creating directory for %s (%s): %s", distro, arch, err)
}
} else {
return fmt.Errorf("error inspecting %s (%s): %s", distro, arch, err)
}
}
if _, err := os.Stat(config.PoolPath(distro, arch)); err != nil {
if os.IsNotExist(err) {
log.Printf("Directory for %s (%s) does not exist, creating", distro, arch)
if err := os.MkdirAll(config.PoolPath(distro, arch), 0755); err != nil {
return fmt.Errorf("error creating directory for %s (%s): %s", distro, arch, err)
}
} else {
return fmt.Errorf("error inspecting %s (%s): %s", distro, arch, err)
}
}
}
}
return nil
}

535
main_test.go Normal file
View File

@ -0,0 +1,535 @@
package main
import (
"bytes"
"compress/gzip"
"crypto/md5"
"encoding/hex"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"testing"
)
var goodOutput = `Package: vim-tiny
Source: vim
Version: 2:7.4.052-1ubuntu3
Architecture: amd64
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Installed-Size: 931
Depends: vim-common (= 2:7.4.052-1ubuntu3), libacl1 (>= 2.2.51-8), libc6 (>= 2.15), libselinux1 (>= 1.32), libtinfo5
Suggests: indent
Provides: editor
Section: editors
Priority: important
Homepage: http://www.vim.org/
Description: Vi IMproved - enhanced vi editor - compact version
Vim is an almost compatible version of the UNIX editor Vi.
.
Many new features have been added: multi level undo, syntax
highlighting, command line history, on-line help, filename
completion, block operations, folding, Unicode support, etc.
.
This package contains a minimal version of vim compiled with no
GUI and a small subset of features in order to keep small the
package size. This package does not depend on the vim-runtime
package, but installing it you will get its additional benefits
(online documentation, plugins, ...).
Original-Maintainer: Debian Vim Maintainers <pkg-vim-maintainers@lists.alioth.debian.org>
`
var goodPkgGzOutput = `Package: vim-tiny
Source: vim
Version: 2:7.4.052-1ubuntu3
Architecture: amd64
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Installed-Size: 931
Depends: vim-common (= 2:7.4.052-1ubuntu3), libacl1 (>= 2.2.51-8), libc6 (>= 2.15), libselinux1 (>= 1.32), libtinfo5
Suggests: indent
Provides: editor
Section: editors
Priority: important
Homepage: http://www.vim.org/
Description: Vi IMproved - enhanced vi editor - compact version
Vim is an almost compatible version of the UNIX editor Vi.
.
Many new features have been added: multi level undo, syntax
highlighting, command line history, on-line help, filename
completion, block operations, folding, Unicode support, etc.
.
This package contains a minimal version of vim compiled with no
GUI and a small subset of features in order to keep small the
package size. This package does not depend on the vim-runtime
package, but installing it you will get its additional benefits
(online documentation, plugins, ...).
Original-Maintainer: Debian Vim Maintainers <pkg-vim-maintainers@lists.alioth.debian.org>
Filename: dists/stable/main/binary-cats/test.deb
Size: 391240
MD5sum: 0ec79417129746ff789fcff0976730c5
SHA1: b2ac976af80f0f50a8336402d5a29c67a2880b9b
SHA256: 9938ec82a8c882ebc2d59b64b0bf2ac01e9cbc5a235be4aa268d4f8484e75eab
`
var goodPkgGzOutputNonDefault = `Package: vim-tiny
Source: vim
Version: 2:7.4.052-1ubuntu3
Architecture: amd64
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Installed-Size: 931
Depends: vim-common (= 2:7.4.052-1ubuntu3), libacl1 (>= 2.2.51-8), libc6 (>= 2.15), libselinux1 (>= 1.32), libtinfo5
Suggests: indent
Provides: editor
Section: editors
Priority: important
Homepage: http://www.vim.org/
Description: Vi IMproved - enhanced vi editor - compact version
Vim is an almost compatible version of the UNIX editor Vi.
.
Many new features have been added: multi level undo, syntax
highlighting, command line history, on-line help, filename
completion, block operations, folding, Unicode support, etc.
.
This package contains a minimal version of vim compiled with no
GUI and a small subset of features in order to keep small the
package size. This package does not depend on the vim-runtime
package, but installing it you will get its additional benefits
(online documentation, plugins, ...).
Original-Maintainer: Debian Vim Maintainers <pkg-vim-maintainers@lists.alioth.debian.org>
Filename: dists/blah/main/binary-cats/test.deb
Size: 391240
MD5sum: 0ec79417129746ff789fcff0976730c5
SHA1: b2ac976af80f0f50a8336402d5a29c67a2880b9b
SHA256: 9938ec82a8c882ebc2d59b64b0bf2ac01e9cbc5a235be4aa268d4f8484e75eab
`
func TestCreateDirs(t *testing.T) {
pwd, err := os.Getwd()
if err != nil {
t.Errorf("Unable to get current working directory: %s", err)
}
config := Conf{ListenPort: "9666", RootRepoPath: pwd + "/testing", SupportArch: []string{"cats", "dogs"}, DistroNames: []string{"stable"}, EnableSSL: false}
// sanity check...
if config.RootRepoPath != pwd+"/testing" {
t.Errorf("RootRepoPath is %s, should be %s\n ", config.RootRepoPath, pwd+"/testing")
}
t.Log("creating temp dirs in ", config.RootRepoPath)
if err := createDirs(config); err != nil {
t.Errorf("createDirs() failed ")
}
for _, archDir := range config.SupportArch {
if _, err := os.Stat(config.RootRepoPath + "/dists/stable/main/binary-" + archDir); err != nil {
if os.IsNotExist(err) {
t.Errorf("Directory for %s does not exist", archDir)
}
}
}
// cleanup
if err := os.RemoveAll(config.RootRepoPath); err != nil {
t.Errorf("error cleaning up after createDirs(): %s", err)
}
// create temp file
tempFile, err := os.Create(pwd + "/tempFile")
if err != nil {
t.Fatalf("create %s: %s", pwd+"/tempFile", err)
}
defer tempFile.Close()
config.RootRepoPath = pwd + "/tempFile"
// Can't make directory named after file.
if err := createDirs(config); err == nil {
t.Errorf("createDirs() should have failed but did not")
}
// cleanup
if err := os.RemoveAll(pwd + "/tempFile"); err != nil {
t.Errorf("error cleaning up after createDirs(): %s", err)
}
}
func TestInspectPackage(t *testing.T) {
parsedControl, err := inspectPackage("samples/vim-tiny_7.4.052-1ubuntu3_amd64.deb")
if err != nil {
t.Error("inspectPackage() error: %s", err)
}
if parsedControl != goodOutput {
t.Errorf("control file does not match")
}
_, err = inspectPackage("thisfileshouldnotexist")
if err == nil {
t.Error("inspectPackage() should have failed, it did not")
}
}
func TestInspectPackageControl(t *testing.T) {
sampleDeb, err := ioutil.ReadFile("samples/control.tar.gz")
if err != nil {
t.Errorf("error opening sample deb file: %s", err)
}
var controlBuf bytes.Buffer
cfReader := bytes.NewReader(sampleDeb)
io.Copy(&controlBuf, cfReader)
parsedControl, err := inspectPackageControl(controlBuf)
if err != nil {
t.Error("error inspecting control file: %s", err)
}
if parsedControl != goodOutput {
t.Errorf("control file does not match")
}
var failControlBuf bytes.Buffer
_, err = inspectPackageControl(failControlBuf)
if err == nil {
t.Error("inspectPackageControl() should have failed, it did not")
}
}
func TestCreatePackagesGz(t *testing.T) {
pwd, err := os.Getwd()
if err != nil {
t.Errorf("Unable to get current working directory: %s", err)
}
config := Conf{ListenPort: "9666", RootRepoPath: pwd + "/testing", SupportArch: []string{"cats", "dogs"}, DistroNames: []string{"stable"}, EnableSSL: false}
// sanity check...
if config.RootRepoPath != pwd+"/testing" {
t.Errorf("RootRepoPath is %s, should be %s\n ", config.RootRepoPath, pwd+"/testing")
}
// copy sample deb to repo location (assuming it exists)
origDeb, err := os.Open("samples/vim-tiny_7.4.052-1ubuntu3_amd64.deb")
if err != nil {
t.Errorf("error opening up sample deb: %s", err)
}
defer origDeb.Close()
for _, archDir := range config.SupportArch {
// do not use the built-in createDirs() in case it is broken
if err := os.MkdirAll(config.RootRepoPath+"/dists/stable/main/binary-"+archDir, 0755); err != nil {
t.Errorf("error creating directory for %s: %s\n", archDir, err)
}
copyDeb, err := os.Create(config.RootRepoPath + "/dists/stable/main/binary-" + archDir + "/test.deb")
if err != nil {
t.Errorf("error creating copy of deb: %s", err)
}
_, err = io.Copy(copyDeb, origDeb)
if err != nil {
t.Errorf("error writing copy of deb: %s", err)
}
if err := copyDeb.Close(); err != nil {
t.Errorf("error saving copy of deb: %s", err)
}
}
if err := createPackagesGz(config, "stable", "cats"); err != nil {
t.Errorf("error creating packages gzip for cats")
}
pkgGzip, err := ioutil.ReadFile(config.RootRepoPath + "/dists/stable/main/binary-cats/Packages.gz")
if err != nil {
t.Errorf("error reading Packages.gz: %s", err)
}
pkgReader, err := gzip.NewReader(bytes.NewReader(pkgGzip))
if err != nil {
t.Errorf("error reading existing Packages.gz: %s", err)
}
buf := bytes.NewBuffer(nil)
io.Copy(buf, pkgReader)
if goodPkgGzOutput != string(buf.Bytes()) {
t.Errorf("Packages.gz does not match, returned value is: %s", string(buf.Bytes()))
}
// cleanup
if err := os.RemoveAll(config.RootRepoPath); err != nil {
t.Errorf("error cleaning up after createPackagesGz(): %s", err)
}
// create temp file
tempFile, err := os.Create(pwd + "/tempFile")
if err != nil {
t.Fatalf("create %s: %s", pwd+"/tempFile", err)
}
defer tempFile.Close()
config.RootRepoPath = pwd + "/tempFile"
// Can't make directory named after file
if err := createPackagesGz(config, "stable", "cats"); err == nil {
t.Errorf("createPackagesGz() should have failed, it did not")
}
// cleanup
if err := os.RemoveAll(pwd + "/tempFile"); err != nil {
t.Errorf("error cleaning up after createDirs(): %s", err)
}
}
func TestCreatePackagesGzNonDefault(t *testing.T) {
pwd, err := os.Getwd()
if err != nil {
t.Errorf("Unable to get current working directory: %s", err)
}
config := Conf{ListenPort: "9666", RootRepoPath: pwd + "/testing", SupportArch: []string{"cats", "dogs"}, DistroNames: []string{"blah"}, EnableSSL: false}
// sanity check...
if config.RootRepoPath != pwd+"/testing" {
t.Errorf("RootRepoPath is %s, should be %s\n ", config.RootRepoPath, pwd+"/testing")
}
// copy sample deb to repo location (assuming it exists)
origDeb, err := os.Open("samples/vim-tiny_7.4.052-1ubuntu3_amd64.deb")
if err != nil {
t.Errorf("error opening up sample deb: %s", err)
}
defer origDeb.Close()
for _, archDir := range config.SupportArch {
// do not use the built-in createDirs() in case it is broken
if err := os.MkdirAll(config.RootRepoPath+"/dists/blah/main/binary-"+archDir, 0755); err != nil {
t.Errorf("error creating directory for %s: %s\n", archDir, err)
}
copyDeb, err := os.Create(config.RootRepoPath + "/dists/blah/main/binary-" + archDir + "/test.deb")
if err != nil {
t.Errorf("error creating copy of deb: %s", err)
}
_, err = io.Copy(copyDeb, origDeb)
if err != nil {
t.Errorf("error writing copy of deb: %s", err)
}
if err := copyDeb.Close(); err != nil {
t.Errorf("error saving copy of deb: %s", err)
}
}
if err := createPackagesGz(config, "blah", "cats"); err != nil {
t.Errorf("error creating packages gzip for cats")
}
pkgGzip, err := ioutil.ReadFile(config.RootRepoPath + "/dists/blah/main/binary-cats/Packages.gz")
if err != nil {
t.Errorf("error reading Packages.gz: %s", err)
}
pkgReader, err := gzip.NewReader(bytes.NewReader(pkgGzip))
if err != nil {
t.Errorf("error reading existing Packages.gz: %s", err)
}
buf := bytes.NewBuffer(nil)
io.Copy(buf, pkgReader)
if goodPkgGzOutputNonDefault != string(buf.Bytes()) {
t.Errorf("Packages.gz does not match, returned value is: %s", string(buf.Bytes()))
}
// cleanup
if err := os.RemoveAll(config.RootRepoPath); err != nil {
t.Errorf("error cleaning up after createPackagesGz(): %s", err)
}
}
func TestUploadHandler(t *testing.T) {
pwd, err := os.Getwd()
if err != nil {
t.Errorf("Unable to get current working directory: %s", err)
}
config := Conf{ListenPort: "9666", RootRepoPath: pwd + "/testing", SupportArch: []string{"cats", "dogs"}, DistroNames: []string{"stable"}, EnableSSL: false}
// sanity check...
if config.RootRepoPath != pwd+"/testing" {
t.Errorf("RootRepoPath is %s, should be %s\n ", config.RootRepoPath, pwd+"/testing")
}
uploadHandle := uploadHandler(config)
// GET
req, _ := http.NewRequest("GET", "", nil)
w := httptest.NewRecorder()
uploadHandle.ServeHTTP(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("uploadHandler GET returned %v, should be %v", w.Code, http.StatusMethodNotAllowed)
}
// POST
// create "all" arch as it's the default
if err := os.MkdirAll(config.RootRepoPath+"/dists/stable/main/binary-all", 0755); err != nil {
t.Error("error creating directory for POST testing: %s", err)
}
sampleDeb, err := os.Open("samples/vim-tiny_7.4.052-1ubuntu3_amd64.deb")
if err != nil {
t.Errorf("error opening sample deb file: %s", err)
}
defer sampleDeb.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "vim-tiny_7.4.052-1ubuntu3_amd64.deb")
if err != nil {
t.Errorf("error FormFile: %s", err)
}
_, err = io.Copy(part, sampleDeb)
if err != nil {
t.Errorf("error copying sampleDeb to FormFile: %s", err)
}
if err := writer.Close(); err != nil {
t.Errorf("error closing form writer: %s", err)
}
req, _ = http.NewRequest("POST", "", body)
req.Header.Add("Content-Type", writer.FormDataContentType())
w = httptest.NewRecorder()
uploadHandle.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("uploadHandler POST returned %v, should be %v", w.Code, http.StatusOK)
}
// verify uploaded file matches sample
uploadFile, _ := ioutil.ReadFile(config.RootRepoPath + "/dists/stable/main/binary-all/vim-tiny_7.4.052-1ubuntu3_amd64.deb")
uploadmd5hash := md5.New()
uploadmd5hash.Write(uploadFile)
uploadFilemd5 := hex.EncodeToString(uploadmd5hash.Sum(nil))
sampleFile, _ := ioutil.ReadFile("samples/vim-tiny_7.4.052-1ubuntu3_amd64.deb")
samplemd5hash := md5.New()
samplemd5hash.Write(sampleFile)
sampleFilemd5 := hex.EncodeToString(samplemd5hash.Sum(nil))
if uploadFilemd5 != sampleFilemd5 {
t.Errorf("uploaded file MD5 is %s, should be %s", uploadFilemd5, sampleFilemd5)
}
// cleanup
if err := os.RemoveAll(config.RootRepoPath); err != nil {
t.Errorf("error cleaning up after uploadHandler(): %s", err)
}
// create temp file
tempFile, err := os.Create(pwd + "/tempFile")
if err != nil {
t.Fatalf("create %s: %s", pwd+"/tempFile", err)
}
defer tempFile.Close()
config.RootRepoPath = pwd + "/tempFile"
// Can't make directory named after file
uploadHandle = uploadHandler(config)
failBody := &bytes.Buffer{}
failWriter := multipart.NewWriter(failBody)
failPart, err := failWriter.CreateFormFile("file", "vim-tiny_7.4.052-1ubuntu3_amd64.deb")
if err != nil {
t.Errorf("error FormFile: %s", err)
}
_, err = io.Copy(failPart, sampleDeb)
if err != nil {
t.Errorf("error copying sampleDeb to FormFile: %s", err)
}
if err := failWriter.Close(); err != nil {
t.Errorf("error closing form writer: %s", err)
}
req, _ = http.NewRequest("POST", "", failBody)
req.Header.Add("Content-Type", failWriter.FormDataContentType())
w = httptest.NewRecorder()
uploadHandle.ServeHTTP(w, req)
if w.Code != http.StatusInternalServerError {
t.Errorf("uploadHandler POST returned %v, should be %v", w.Code, http.StatusInternalServerError)
}
// cleanup
if err := os.RemoveAll(pwd + "/tempFile"); err != nil {
t.Errorf("error cleaning up after createDirs(): %s", err)
}
}
func TestDeleteHandler(t *testing.T) {
pwd, err := os.Getwd()
if err != nil {
t.Errorf("Unable to get current working directory: %s", err)
}
config := Conf{ListenPort: "9666", RootRepoPath: pwd + "/testing", SupportArch: []string{"cats", "dogs"}, DistroNames: []string{"stable"}, EnableSSL: false}
// sanity check...
if config.RootRepoPath != pwd+"/testing" {
t.Errorf("RootRepoPath is %s, should be %s\n ", config.RootRepoPath, pwd+"/testing")
}
deleteHandle := deleteHandler(config)
// GET
req, _ := http.NewRequest("GET", "", nil)
w := httptest.NewRecorder()
deleteHandle.ServeHTTP(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("deleteHandler GET returned %v, should be %v", w.Code, http.StatusMethodNotAllowed)
}
// DELETE
// create "all" arch as it's the default
if err := os.MkdirAll(config.RootRepoPath+"/dists/stable/main/binary-all", 0755); err != nil {
t.Error("error creating directory for POST testing: %s", err)
}
tempDeb, err := os.Create(config.RootRepoPath + "/dists/stable/main/binary-all/myapp.deb")
if err != nil {
t.Fatalf("create %s: %s", config.RootRepoPath+"/dists/stable/main/binary-all/myapp.deb", err)
}
defer tempDeb.Close()
req, _ = http.NewRequest("DELETE", "", bytes.NewBufferString("{\"filename\":\"myapp.deb\",\"arch\":\"all\", \"distroName\":\"stable\"}"))
w = httptest.NewRecorder()
deleteHandle.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("deleteHandler DELETE returned %v, should be %v", w.Code, http.StatusOK)
}
// cleanup
if err := os.RemoveAll(config.RootRepoPath); err != nil {
t.Errorf("error cleaning up after uploadHandler(): %s", err)
}
// create temp file
tempFile, err := os.Create(pwd + "/tempFile")
if err != nil {
t.Fatalf("create %s: %s", pwd+"/tempFile", err)
}
defer tempFile.Close()
config.RootRepoPath = pwd + "/tempFile"
// Can't make directory named after file
deleteHandle = deleteHandler(config)
req, _ = http.NewRequest("DELETE", "", bytes.NewBufferString("{\"filename\":\"myapp.deb\",\"arch\":\"amd64\", \"distroName\":\"stable\"}"))
w = httptest.NewRecorder()
deleteHandle.ServeHTTP(w, req)
if w.Code != http.StatusInternalServerError {
t.Errorf("deleteHandler DELETE returned %v, should be %v", w.Code, http.StatusInternalServerError)
}
// cleanup
if err := os.RemoveAll(pwd + "/tempFile"); err != nil {
t.Errorf("error cleaning up after createDirs(): %s", err)
}
}
func BenchmarkUploadHandler(b *testing.B) {
pwd, err := os.Getwd()
if err != nil {
b.Errorf("Unable to get current working directory: %s", err)
}
config := &Conf{ListenPort: "9666", RootRepoPath: pwd + "/testing", SupportArch: []string{"cats", "dogs"}, DistroNames: []string{"stable"}, EnableSSL: false}
// sanity check...
if config.RootRepoPath != pwd+"/testing" {
b.Errorf("RootRepoPath is %s, should be %s\n ", config.RootRepoPath, pwd+"/testing")
}
uploadHandle := uploadHandler(*config)
if err := os.MkdirAll(config.RootRepoPath+"/dists/stable/main/binary-all", 0755); err != nil {
b.Errorf("error creating directory for POST testing: %s", err)
}
sampleDeb, err := os.Open("samples/vim-tiny_7.4.052-1ubuntu3_amd64.deb")
if err != nil {
b.Errorf("error opening sample deb file: %s", err)
}
defer sampleDeb.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// temporary (i hope) hack to solve "http: MultipartReader called twice" error
b.StopTimer()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "vim-tiny_7.4.052-1ubuntu3_amd64.deb")
if err != nil {
b.Errorf("error FormFile: %s", err)
}
if _, err := io.Copy(part, sampleDeb); err != nil {
b.Errorf("error copying sampleDeb to FormFile: %s", err)
}
if err := writer.Close(); err != nil {
b.Errorf("error closing form writer: %s", err)
}
req, _ := http.NewRequest("POST", "/upload?distro=stable", body)
req.Header.Add("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
b.StartTimer()
uploadHandle.ServeHTTP(w, req)
}
b.StopTimer()
// cleanup
_ = os.RemoveAll(config.RootRepoPath)
}

31
packages.go Normal file
View File

@ -0,0 +1,31 @@
package main
import (
"regexp"
"log"
"reflect"
)
var (
variableRegexp = regexp.MustCompile("(.*?):\\s*(.*)")
)
type Package struct {
Package string
Version string
}
func parsePackageData(ctlData string) (*Package, error) {
res := &Package{}
for _, match := range variableRegexp.FindAllStringSubmatch(ctlData, -1) {
switch match[1] {
case "Package":
res.Package = match[2]
case "Version":
res.Version = match[2]
}
}
return res, nil
}

12
sample_conf.json Normal file
View File

@ -0,0 +1,12 @@
{
"listenPort" : "9090",
"rootRepoPath" : "/opt/deb-simple/repo",
"supportedArch" : ["all","i386","amd64"],
"distroNames":["stable"],
"pgpSecretKey": "secring.gpg",
"pgpPassphrase" : "",
"enableSSL" : false,
"SSLcert" : "server.crt",
"SSLkey" : "server.key",
"key" : "abcdefg"
}

BIN
samples/control.tar.gz Normal file

Binary file not shown.

Binary file not shown.