add version info

This commit is contained in:
Nicola Murino 2019-08-08 10:01:33 +02:00
parent 2aca4479a5
commit 4f4489d3f1
16 changed files with 160 additions and 29 deletions

View file

@ -40,6 +40,17 @@ $ go get -u github.com/drakkan/sftpgo
Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
Version info can be embedded populating the following variables at build time:
- `github.com/drakkan/sftpgo/utils.commit`
- `github.com/drakkan/sftpgo/utils.date`
For example on Linux you can build using the following ldflags:
```bash
-ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --tags --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date --utc +%FT%TZ`"
```
A systemd sample [service](https://github.com/drakkan/sftpgo/tree/master/init/sftpgo.service "systemd service") can be found inside the source tree.
Alternately you can use distro packages:
@ -195,7 +206,7 @@ For each account the following properties can be configured:
- `username`
- `password` used for password authentication. For users created using SFTPGo REST API the password will be stored using argon2id hashing algo. SFTPGo supports checking passwords stored with bcrypt too. Currently, as fallback, there is a clear text password checking but you should not store passwords as clear text and this support could be removed at any time, so please don't depend on it.
- `public_key` array of public keys. At least one public key or the password is mandatory.
- `public_keys` array of public keys. At least one public key or the password is mandatory.
- `home_dir` The user cannot upload or download files outside this directory. Must be an absolute path
- `uid`, `gid`. If sftpgo runs as root system user then the created files and directories will be assigned to this system uid/gid. Ignored on windows and if sftpgo runs as non root user: in this case files and directories for all SFTP users will be owned by the system user that runs sftpgo.
- `max_sessions` maximum concurrent sessions. 0 means unlimited
@ -223,7 +234,7 @@ If quota tracking is enabled in `sftpgo` configuration file, then the used size
REST API is designed to run on localhost or on a trusted network, if you need HTTPS or authentication you can setup a reverse proxy using an HTTP Server such as Apache or NGNIX.
For example you can setup a reverse proxy using apache this way:
For example you can keep SFTPGo listening on localhost and expose it externally configuring a reverse proxy using Apache HTTP Server this way:
```
ProxyPass /api/v1 http://127.0.0.1:8080/api/v1
@ -236,7 +247,8 @@ and you can add authentication with something like this:
<Location /api/v1>
AuthType Digest
AuthName "Private"
AuthBasicProvider file
AuthDigestDomain "/api/v1"
AuthDigestProvider file
AuthUserFile "/etc/httpd/conf/auth_digest"
Require valid-user
</Location>
@ -248,7 +260,7 @@ The OpenAPI 3 schema for the exposed API can be found inside the source tree: [o
A sample CLI client for the REST API can be found inside the source tree [scripts](https://github.com/drakkan/sftpgo/tree/master/scripts "scripts") directory.
You can also generate your own REST client using an OpenAPI generator such as [swagger-codegen](https://github.com/swagger-api/swagger-codegen) or [OpenAPI Generator](https://openapi-generator.tech/)
You can also generate your own REST client, in your preferred programming language or even bash scripts, using an OpenAPI generator such as [swagger-codegen](https://github.com/swagger-api/swagger-codegen) or [OpenAPI Generator](https://openapi-generator.tech/)
## Logs

View file

@ -18,6 +18,7 @@ const (
activeConnectionsPath = "/api/v1/sftp_connection"
quotaScanPath = "/api/v1/quota_scan"
userPath = "/api/v1/user"
versionPath = "/api/v1/version"
)
var (

View file

@ -35,6 +35,7 @@ const (
userPath = "/api/v1/user"
activeConnectionsPath = "/api/v1/sftp_connection"
quotaScanPath = "/api/v1/quota_scan"
versionPath = "/api/v1/version"
)
var (
@ -393,6 +394,17 @@ func TestStartQuotaScan(t *testing.T) {
}
}
func TestGetVersion(t *testing.T) {
_, _, err := api.GetVersion(http.StatusOK)
if err != nil {
t.Errorf("unable to get sftp version: %v", err)
}
_, _, err = api.GetVersion(http.StatusInternalServerError)
if err == nil {
t.Errorf("get version request must succeed, we requested to check a wrong status code")
}
}
func TestGetSFTPConnections(t *testing.T) {
_, _, err := api.GetSFTPConnections(http.StatusOK)
if err != nil {
@ -668,6 +680,12 @@ func TestStartQuotaScanNonExistentUserMock(t *testing.T) {
checkResponseCode(t, http.StatusBadRequest, rr.Code)
}
func TestGetVersionMock(t *testing.T) {
req, _ := http.NewRequest(http.MethodGet, versionPath, nil)
rr := executeRequest(req)
checkResponseCode(t, http.StatusOK, rr.Code)
}
func TestGetSFTPConnectionsMock(t *testing.T) {
req, _ := http.NewRequest(http.MethodGet, activeConnectionsPath, nil)
rr := executeRequest(req)

View file

@ -242,6 +242,24 @@ func CloseSFTPConnection(connectionID string, expectedStatusCode int) ([]byte, e
return body, err
}
// GetVersion returns version details
func GetVersion(expectedStatusCode int) (utils.VersionInfo, []byte, error) {
var version utils.VersionInfo
var body []byte
resp, err := getHTTPClient().Get(buildURLRelativeToBase(versionPath))
if err != nil {
return version, body, err
}
defer resp.Body.Close()
err = checkResponse(resp.StatusCode, expectedStatusCode)
if err == nil && expectedStatusCode == http.StatusOK {
err = render.DecodeJSON(resp.Body, &version)
} else {
body, _ = getResponseBody(resp)
}
return version, body, err
}
func checkResponse(actual int, expected int) error {
if expected != actual {
return fmt.Errorf("wrong status code: got %v want %v", actual, expected)

View file

@ -205,5 +205,9 @@ func TestApiCallToNotListeningServer(t *testing.T) {
if err == nil {
t.Errorf("request to an inactive URL must fail")
}
_, _, err = GetVersion(http.StatusOK)
if err == nil {
t.Errorf("request to an inactive URL must fail")
}
SetBaseURL(oldBaseURL)
}

View file

@ -5,6 +5,7 @@ import (
"github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/sftpd"
"github.com/drakkan/sftpgo/utils"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/render"
@ -30,6 +31,10 @@ func initializeRouter() {
sendAPIResponse(w, r, nil, "Method not allowed", http.StatusMethodNotAllowed)
}))
router.Get(versionPath, func(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, utils.GetAppVersion())
})
router.Get(activeConnectionsPath, func(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, sftpd.GetConnectionsStats())
})

View file

@ -7,6 +7,21 @@ info:
servers:
- url: /api/v1
paths:
/version:
get:
tags:
- version
summary: Get version details
operationId: get_version
responses:
200:
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref : '#/components/schemas/VersionInfo'
/sftp_connection:
get:
tags:
@ -654,3 +669,13 @@ components:
type: string
nullable: true
description: error description if any
VersionInfo:
type: object
properties:
version:
type: string
build_date:
type: string
commit_hash:
type: string

View file

@ -20,9 +20,10 @@ var (
)
func init() {
version := utils.GetAppVersion()
rootCmd.Flags().BoolP("version", "v", false, "")
rootCmd.Version = utils.GetAppVersion()
rootCmd.SetVersionTemplate(`{{printf "SFTPGo version "}}{{printf "%s" .Version}}
rootCmd.Version = version.GetVersionAsString()
rootCmd.SetVersionTemplate(`{{printf "SFTPGo version: "}}{{printf "%s" .Version}}
`)
}

View file

@ -7,7 +7,6 @@ package config
import (
"fmt"
"runtime"
"strings"
"github.com/drakkan/sftpgo/api"
@ -79,10 +78,7 @@ func init() {
replacer := strings.NewReplacer(".", "__")
viper.SetEnvKeyReplacer(replacer)
viper.SetConfigName(DefaultConfigName)
if runtime.GOOS == "linux" {
viper.AddConfigPath("$HOME/.config/sftpgo")
viper.AddConfigPath("/etc/sftpgo")
}
setViperAdditionalConfigPaths()
viper.AddConfigPath(".")
viper.AutomaticEnv()
}

11
config/config_linux.go Normal file
View file

@ -0,0 +1,11 @@
// +build linux
package config
import "github.com/spf13/viper"
// linux specific config search path
func setViperAdditionalConfigPaths() {
viper.AddConfigPath("$HOME/.config/sftpgo")
viper.AddConfigPath("/etc/sftpgo")
}

7
config/config_nolinux.go Normal file
View file

@ -0,0 +1,7 @@
// +build !linux
package config
func setViperAdditionalConfigPaths() {
}

View file

@ -16,6 +16,7 @@ class SFTPGoApiRequests:
self.userPath = urlparse.urljoin(baseUrl, "/api/v1/user")
self.quotaScanPath = urlparse.urljoin(baseUrl, "/api/v1/quota_scan")
self.activeConnectionsPath = urlparse.urljoin(baseUrl, "/api/v1/sftp_connection")
self.versionPath = urlparse.urljoin(baseUrl, "/api/v1/version")
self.debug = debug
if authType == "basic":
self.auth = requests.auth.HTTPBasicAuth(authUser, authPassword)
@ -98,6 +99,10 @@ class SFTPGoApiRequests:
r = requests.post(self.quotaScanPath, json=u, auth=self.auth, verify=self.verify)
self.printResponse(r)
def getVersion(self):
r = requests.get(self.versionPath, auth=self.auth, verify=self.verify)
self.printResponse(r)
def addCommonUserArguments(parser):
parser.add_argument('username', type=str)
@ -165,6 +170,8 @@ if __name__ == '__main__':
parserStartQuotaScans = subparsers.add_parser("start_quota_scan", help="Start a new quota scan")
addCommonUserArguments(parserStartQuotaScans)
parserGetVersion = subparsers.add_parser("get_version", help="Get version details")
args = parser.parse_args()
api = SFTPGoApiRequests(args.debug, args.base_url, args.auth_type, args.auth_user, args.auth_password, args.verify)
@ -191,4 +198,6 @@ if __name__ == '__main__':
api.getQuotaScans()
elif args.command == "start_quota_scan":
api.startQuotaScan(args.username)
elif args.command == "get_version":
api.getVersion()

View file

@ -193,7 +193,7 @@ func GetConnectionsStats() []ConnectionStatus {
}
for _, t := range activeTransfers {
if t.connectionID == c.ID {
if utils.GetTimeAsMsSinceEpoch(t.lastActivity) > conn.LastActivity {
if t.lastActivity.UnixNano() > c.lastActivity.UnixNano() {
conn.LastActivity = utils.GetTimeAsMsSinceEpoch(t.lastActivity)
}
var operationType string

View file

@ -96,6 +96,7 @@ func TestMain(m *testing.M) {
sftpdConf := config.GetSFTPDConfig()
httpdConf := config.GetHTTPDConfig()
router := api.GetHTTPRouter()
sftpdConf.BindPort = 2022
// we run the test cases with UploadMode atomic. The non atomic code path
// simply does not execute some code so if it works in atomic mode will
// work in non atomic mode too
@ -105,7 +106,7 @@ func TestMain(m *testing.M) {
} else {
homeBasePath = "/tmp"
sftpdConf.Actions.ExecuteOn = []string{"download", "upload", "rename", "delete"}
sftpdConf.Actions.Command = "/bin/true"
sftpdConf.Actions.Command = "/usr/bin/true"
sftpdConf.Actions.HTTPNotificationURL = "http://127.0.0.1:8080/"
}
@ -145,6 +146,7 @@ func TestInitialization(t *testing.T) {
config.LoadConfig(configDir, "")
sftpdConf := config.GetSFTPDConfig()
sftpdConf.Umask = "invalid umask"
sftpdConf.BindPort = 2022
err := sftpdConf.Initialize(configDir)
if err == nil {
t.Errorf("Inizialize must fail, a SFTP server should be already running")

View file

@ -12,12 +12,6 @@ import (
const logSender = "utils"
var (
version = "dev"
commit = ""
date = ""
)
// IsStringInSlice searches a string in a slice and returns true if the string is found
func IsStringInSlice(obj string, list []string) bool {
for _, v := range list {
@ -76,14 +70,7 @@ func SetPathPermissions(path string, uid int, gid int) {
}
}
// GetAppVersion returns the app version
func GetAppVersion() string {
v := version
if len(commit) > 0 {
v += "-" + commit
}
if len(date) > 0 {
v += "-" + date
}
return v
// GetAppVersion returns VersionInfo struct
func GetAppVersion() VersionInfo {
return versionInfo
}

35
utils/version.go Normal file
View file

@ -0,0 +1,35 @@
package utils
var (
version = "0.9.0-dev"
commit = ""
date = ""
versionInfo VersionInfo
)
// VersionInfo defines version details
type VersionInfo struct {
Version string `json:"version"`
BuildDate string `json:"build_date"`
CommitHash string `json:"commit_hash"`
}
// GetVersionAsString returns the string representation of the VersionInfo struct
func (v *VersionInfo) GetVersionAsString() string {
versionString := v.Version
if len(v.CommitHash) > 0 {
versionString += "-" + v.CommitHash
}
if len(v.BuildDate) > 0 {
versionString += "-" + v.BuildDate
}
return versionString
}
func init() {
versionInfo = VersionInfo{
Version: version,
CommitHash: commit,
BuildDate: date,
}
}