Add support for Vagrant packages (#20930)
* Add support for Vagrant boxes. * Add authentication. * Add tests. * Add integration tests. * Add docs. * Add icons. * Update routers/api/packages/api.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: 6543 <6543@obermui.de>tokarchuk/v1.18
parent
8a66b01e55
commit
41c76ad714
@ -0,0 +1,78 @@ |
||||
--- |
||||
date: "2022-08-23T00:00:00+00:00" |
||||
title: "Vagrant Packages Repository" |
||||
slug: "packages/vagrant" |
||||
draft: false |
||||
toc: false |
||||
menu: |
||||
sidebar: |
||||
parent: "packages" |
||||
name: "vagrant" |
||||
weight: 120 |
||||
identifier: "vagrant" |
||||
--- |
||||
|
||||
# Vagrant Packages Repository |
||||
|
||||
Publish [Vagrant](https://www.vagrantup.com/) packages for your user or organization. |
||||
|
||||
**Table of Contents** |
||||
|
||||
{{< toc >}} |
||||
|
||||
## Requirements |
||||
|
||||
To work with the Vagrant package registry, you need [Vagrant](https://www.vagrantup.com/downloads) and a tool to make HTTP requests like `curl`. |
||||
|
||||
## Publish a package |
||||
|
||||
Publish a Vagrant box by performing a HTTP PUT request to the registry: |
||||
|
||||
``` |
||||
PUT https://gitea.example.com/api/packages/{owner}/vagrant/{package_name}/{package_version}/{provider}.box |
||||
``` |
||||
|
||||
| Parameter | Description | |
||||
| ----------------- | ----------- | |
||||
| `owner` | The owner of the package. | |
||||
| `package_name` | The package name. | |
||||
| `package_version` | The package version, semver compatible. | |
||||
| `provider` | One of the [supported provider names](https://www.vagrantup.com/docs/providers). | |
||||
|
||||
Example for uploading a Hyper-V box: |
||||
|
||||
```shell |
||||
curl --user your_username:your_password_or_token \ |
||||
--upload-file path/to/your/vagrant.box \ |
||||
https://gitea.example.com/api/packages/testuser/vagrant/test_system/1.0.0/hyperv.box |
||||
``` |
||||
|
||||
You cannot publish a box if a box of the same name, version and provider already exists. You must delete the existing package first. |
||||
|
||||
## Install a package |
||||
|
||||
To install a box from the package registry, execute the following command: |
||||
|
||||
```shell |
||||
vagrant box add "https://gitea.example.com/api/packages/{owner}/vagrant/{package_name}" |
||||
``` |
||||
|
||||
| Parameter | Description | |
||||
| -------------- | ----------- | |
||||
| `owner` | The owner of the package. | |
||||
| `package_name` | The package name. | |
||||
|
||||
For example: |
||||
|
||||
```shell |
||||
vagrant box add "https://gitea.example.com/api/packages/testuser/vagrant/test_system" |
||||
``` |
||||
|
||||
This will install the latest version of the package. To add a specific version, use the `--box-version` parameter. |
||||
If the registry is private you can pass your [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) in the `VAGRANT_CLOUD_TOKEN` environment variable. |
||||
|
||||
## Supported commands |
||||
|
||||
``` |
||||
vagrant box add |
||||
``` |
@ -0,0 +1,170 @@ |
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package integrations |
||||
|
||||
import ( |
||||
"archive/tar" |
||||
"bytes" |
||||
"compress/gzip" |
||||
"fmt" |
||||
"net/http" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
"code.gitea.io/gitea/models/packages" |
||||
"code.gitea.io/gitea/models/unittest" |
||||
user_model "code.gitea.io/gitea/models/user" |
||||
"code.gitea.io/gitea/modules/json" |
||||
vagrant_module "code.gitea.io/gitea/modules/packages/vagrant" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestPackageVagrant(t *testing.T) { |
||||
defer prepareTestEnv(t)() |
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) |
||||
|
||||
token := "Bearer " + getUserToken(t, user.Name) |
||||
|
||||
packageName := "test_package" |
||||
packageVersion := "1.0.1" |
||||
packageDescription := "Test Description" |
||||
packageProvider := "virtualbox" |
||||
|
||||
filename := fmt.Sprintf("%s.box", packageProvider) |
||||
|
||||
infoContent, _ := json.Marshal(map[string]string{ |
||||
"description": packageDescription, |
||||
}) |
||||
|
||||
var buf bytes.Buffer |
||||
zw := gzip.NewWriter(&buf) |
||||
archive := tar.NewWriter(zw) |
||||
archive.WriteHeader(&tar.Header{ |
||||
Name: "info.json", |
||||
Mode: 0o600, |
||||
Size: int64(len(infoContent)), |
||||
}) |
||||
archive.Write(infoContent) |
||||
archive.Close() |
||||
zw.Close() |
||||
content := buf.Bytes() |
||||
|
||||
root := fmt.Sprintf("/api/packages/%s/vagrant", user.Name) |
||||
|
||||
t.Run("Authenticate", func(t *testing.T) { |
||||
defer PrintCurrentTest(t)() |
||||
|
||||
authenticateURL := fmt.Sprintf("%s/authenticate", root) |
||||
|
||||
req := NewRequest(t, "GET", authenticateURL) |
||||
MakeRequest(t, req, http.StatusUnauthorized) |
||||
|
||||
req = NewRequest(t, "GET", authenticateURL) |
||||
addTokenAuthHeader(req, token) |
||||
MakeRequest(t, req, http.StatusOK) |
||||
}) |
||||
|
||||
boxURL := fmt.Sprintf("%s/%s", root, packageName) |
||||
|
||||
t.Run("Upload", func(t *testing.T) { |
||||
defer PrintCurrentTest(t)() |
||||
|
||||
req := NewRequest(t, "HEAD", boxURL) |
||||
MakeRequest(t, req, http.StatusNotFound) |
||||
|
||||
uploadURL := fmt.Sprintf("%s/%s/%s", boxURL, packageVersion, filename) |
||||
|
||||
req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)) |
||||
MakeRequest(t, req, http.StatusUnauthorized) |
||||
|
||||
req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)) |
||||
addTokenAuthHeader(req, token) |
||||
MakeRequest(t, req, http.StatusCreated) |
||||
|
||||
req = NewRequest(t, "HEAD", boxURL) |
||||
resp := MakeRequest(t, req, http.StatusOK) |
||||
assert.True(t, strings.HasPrefix(resp.HeaderMap.Get("Content-Type"), "application/json")) |
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeVagrant) |
||||
assert.NoError(t, err) |
||||
assert.Len(t, pvs, 1) |
||||
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) |
||||
assert.NoError(t, err) |
||||
assert.NotNil(t, pd.SemVer) |
||||
assert.IsType(t, &vagrant_module.Metadata{}, pd.Metadata) |
||||
assert.Equal(t, packageName, pd.Package.Name) |
||||
assert.Equal(t, packageVersion, pd.Version.Version) |
||||
|
||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) |
||||
assert.NoError(t, err) |
||||
assert.Len(t, pfs, 1) |
||||
assert.Equal(t, filename, pfs[0].Name) |
||||
assert.True(t, pfs[0].IsLead) |
||||
|
||||
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, int64(len(content)), pb.Size) |
||||
|
||||
req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)) |
||||
addTokenAuthHeader(req, token) |
||||
MakeRequest(t, req, http.StatusConflict) |
||||
}) |
||||
|
||||
t.Run("Download", func(t *testing.T) { |
||||
defer PrintCurrentTest(t)() |
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", boxURL, packageVersion, filename)) |
||||
resp := MakeRequest(t, req, http.StatusOK) |
||||
|
||||
assert.Equal(t, content, resp.Body.Bytes()) |
||||
}) |
||||
|
||||
t.Run("EnumeratePackageVersions", func(t *testing.T) { |
||||
defer PrintCurrentTest(t)() |
||||
|
||||
req := NewRequest(t, "GET", boxURL) |
||||
resp := MakeRequest(t, req, http.StatusOK) |
||||
|
||||
type providerData struct { |
||||
Name string `json:"name"` |
||||
URL string `json:"url"` |
||||
Checksum string `json:"checksum"` |
||||
ChecksumType string `json:"checksum_type"` |
||||
} |
||||
|
||||
type versionMetadata struct { |
||||
Version string `json:"version"` |
||||
Status string `json:"status"` |
||||
DescriptionHTML string `json:"description_html,omitempty"` |
||||
DescriptionMarkdown string `json:"description_markdown,omitempty"` |
||||
Providers []*providerData `json:"providers"` |
||||
} |
||||
|
||||
type packageMetadata struct { |
||||
Name string `json:"name"` |
||||
Description string `json:"description,omitempty"` |
||||
ShortDescription string `json:"short_description,omitempty"` |
||||
Versions []*versionMetadata `json:"versions"` |
||||
} |
||||
|
||||
var result packageMetadata |
||||
DecodeJSON(t, resp, &result) |
||||
|
||||
assert.Equal(t, packageName, result.Name) |
||||
assert.Equal(t, packageDescription, result.Description) |
||||
assert.Len(t, result.Versions, 1) |
||||
version := result.Versions[0] |
||||
assert.Equal(t, packageVersion, version.Version) |
||||
assert.Equal(t, "active", version.Status) |
||||
assert.Len(t, version.Providers, 1) |
||||
provider := version.Providers[0] |
||||
assert.Equal(t, packageProvider, provider.Name) |
||||
assert.Equal(t, "sha512", provider.ChecksumType) |
||||
assert.Equal(t, "259bebd6160acad695016d22a45812e26f187aaf78e71a4c23ee3201528346293f991af3468a8c6c5d2a21d7d9e1bdc1bf79b87110b2fddfcc5a0d45963c7c30", provider.Checksum) |
||||
}) |
||||
} |
@ -0,0 +1,97 @@ |
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vagrant |
||||
|
||||
import ( |
||||
"archive/tar" |
||||
"compress/gzip" |
||||
"io" |
||||
"strings" |
||||
|
||||
"code.gitea.io/gitea/modules/json" |
||||
"code.gitea.io/gitea/modules/validation" |
||||
) |
||||
|
||||
const ( |
||||
PropertyProvider = "vagrant.provider" |
||||
) |
||||
|
||||
// Metadata represents the metadata of a Vagrant package
|
||||
type Metadata struct { |
||||
Author string `json:"author,omitempty"` |
||||
Description string `json:"description,omitempty"` |
||||
ProjectURL string `json:"project_url,omitempty"` |
||||
RepositoryURL string `json:"repository_url,omitempty"` |
||||
} |
||||
|
||||
// ParseMetadataFromBox parses the metdata of a box file
|
||||
func ParseMetadataFromBox(r io.Reader) (*Metadata, error) { |
||||
gzr, err := gzip.NewReader(r) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer gzr.Close() |
||||
|
||||
tr := tar.NewReader(gzr) |
||||
for { |
||||
hd, err := tr.Next() |
||||
if err == io.EOF { |
||||
break |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if hd.Typeflag != tar.TypeReg { |
||||
continue |
||||
} |
||||
|
||||
if hd.Name == "info.json" { |
||||
return ParseInfoFile(tr) |
||||
} |
||||
} |
||||
|
||||
return &Metadata{}, nil |
||||
} |
||||
|
||||
// ParseInfoFile parses a info.json file to retrieve the metadata of a Vagrant package
|
||||
func ParseInfoFile(r io.Reader) (*Metadata, error) { |
||||
var values map[string]string |
||||
if err := json.NewDecoder(r).Decode(&values); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
m := &Metadata{} |
||||
|
||||
// There is no defined format for this file, just try the common keys
|
||||
for k, v := range values { |
||||
switch strings.ToLower(k) { |
||||
case "description": |
||||
fallthrough |
||||
case "short_description": |
||||
m.Description = v |
||||
case "website": |
||||
fallthrough |
||||
case "homepage": |
||||
fallthrough |
||||
case "url": |
||||
if validation.IsValidURL(v) { |
||||
m.ProjectURL = v |
||||
} |
||||
case "repository": |
||||
fallthrough |
||||
case "source": |
||||
if validation.IsValidURL(v) { |
||||
m.RepositoryURL = v |
||||
} |
||||
case "author": |
||||
fallthrough |
||||
case "authors": |
||||
m.Author = v |
||||
} |
||||
} |
||||
|
||||
return m, nil |
||||
} |
@ -0,0 +1,111 @@ |
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vagrant |
||||
|
||||
import ( |
||||
"archive/tar" |
||||
"bytes" |
||||
"compress/gzip" |
||||
"io" |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/modules/json" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
const ( |
||||
author = "gitea" |
||||
description = "Package Description" |
||||
projectURL = "https://gitea.io" |
||||
repositoryURL = "https://gitea.io/gitea/gitea" |
||||
) |
||||
|
||||
func TestParseMetadataFromBox(t *testing.T) { |
||||
createArchive := func(files map[string][]byte) io.Reader { |
||||
var buf bytes.Buffer |
||||
zw := gzip.NewWriter(&buf) |
||||
tw := tar.NewWriter(zw) |
||||
for filename, content := range files { |
||||
hdr := &tar.Header{ |
||||
Name: filename, |
||||
Mode: 0o600, |
||||
Size: int64(len(content)), |
||||
} |
||||
tw.WriteHeader(hdr) |
||||
tw.Write(content) |
||||
} |
||||
tw.Close() |
||||
zw.Close() |
||||
return &buf |
||||
} |
||||
|
||||
t.Run("MissingInfoFile", func(t *testing.T) { |
||||
data := createArchive(map[string][]byte{"dummy.txt": {}}) |
||||
|
||||
metadata, err := ParseMetadataFromBox(data) |
||||
assert.NotNil(t, metadata) |
||||
assert.NoError(t, err) |
||||
}) |
||||
|
||||
t.Run("Valid", func(t *testing.T) { |
||||
content, err := json.Marshal(map[string]string{ |
||||
"description": description, |
||||
"author": author, |
||||
"website": projectURL, |
||||
"repository": repositoryURL, |
||||
}) |
||||
assert.NoError(t, err) |
||||
|
||||
data := createArchive(map[string][]byte{"info.json": content}) |
||||
|
||||
metadata, err := ParseMetadataFromBox(data) |
||||
assert.NotNil(t, metadata) |
||||
assert.NoError(t, err) |
||||
|
||||
assert.Equal(t, author, metadata.Author) |
||||
assert.Equal(t, description, metadata.Description) |
||||
assert.Equal(t, projectURL, metadata.ProjectURL) |
||||
assert.Equal(t, repositoryURL, metadata.RepositoryURL) |
||||
}) |
||||
} |
||||
|
||||
func TestParseInfoFile(t *testing.T) { |
||||
t.Run("UnknownKeys", func(t *testing.T) { |
||||
content, err := json.Marshal(map[string]string{ |
||||
"package": "", |
||||
"dummy": "", |
||||
}) |
||||
assert.NoError(t, err) |
||||
|
||||
metadata, err := ParseInfoFile(bytes.NewReader(content)) |
||||
assert.NotNil(t, metadata) |
||||
assert.NoError(t, err) |
||||
|
||||
assert.Empty(t, metadata.Author) |
||||
assert.Empty(t, metadata.Description) |
||||
assert.Empty(t, metadata.ProjectURL) |
||||
assert.Empty(t, metadata.RepositoryURL) |
||||
}) |
||||
|
||||
t.Run("Valid", func(t *testing.T) { |
||||
content, err := json.Marshal(map[string]string{ |
||||
"description": description, |
||||
"author": author, |
||||
"website": projectURL, |
||||
"repository": repositoryURL, |
||||
}) |
||||
assert.NoError(t, err) |
||||
|
||||
metadata, err := ParseInfoFile(bytes.NewReader(content)) |
||||
assert.NotNil(t, metadata) |
||||
assert.NoError(t, err) |
||||
|
||||
assert.Equal(t, author, metadata.Author) |
||||
assert.Equal(t, description, metadata.Description) |
||||
assert.Equal(t, projectURL, metadata.ProjectURL) |
||||
assert.Equal(t, repositoryURL, metadata.RepositoryURL) |
||||
}) |
||||
} |
After Width: | Height: | Size: 597 B |
@ -0,0 +1,239 @@ |
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vagrant |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"sort" |
||||
"strings" |
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages" |
||||
"code.gitea.io/gitea/modules/context" |
||||
packages_module "code.gitea.io/gitea/modules/packages" |
||||
vagrant_module "code.gitea.io/gitea/modules/packages/vagrant" |
||||
"code.gitea.io/gitea/modules/setting" |
||||
"code.gitea.io/gitea/routers/api/packages/helper" |
||||
packages_service "code.gitea.io/gitea/services/packages" |
||||
|
||||
"github.com/hashicorp/go-version" |
||||
) |
||||
|
||||
func apiError(ctx *context.Context, status int, obj interface{}) { |
||||
helper.LogAndProcessError(ctx, status, obj, func(message string) { |
||||
ctx.JSON(status, struct { |
||||
Errors []string `json:"errors"` |
||||
}{ |
||||
Errors: []string{ |
||||
message, |
||||
}, |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
func CheckAuthenticate(ctx *context.Context) { |
||||
if ctx.Doer == nil { |
||||
apiError(ctx, http.StatusUnauthorized, "Invalid access token") |
||||
return |
||||
} |
||||
|
||||
ctx.Status(http.StatusOK) |
||||
} |
||||
|
||||
func CheckBoxAvailable(ctx *context.Context) { |
||||
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeVagrant, ctx.Params("name")) |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
if len(pvs) == 0 { |
||||
apiError(ctx, http.StatusNotFound, err) |
||||
return |
||||
} |
||||
|
||||
ctx.JSON(http.StatusOK, nil) // needs to be Content-Type: application/json
|
||||
} |
||||
|
||||
type packageMetadata struct { |
||||
Name string `json:"name"` |
||||
Description string `json:"description,omitempty"` |
||||
ShortDescription string `json:"short_description,omitempty"` |
||||
Versions []*versionMetadata `json:"versions"` |
||||
} |
||||
|
||||
type versionMetadata struct { |
||||
Version string `json:"version"` |
||||
Status string `json:"status"` |
||||
DescriptionHTML string `json:"description_html,omitempty"` |
||||
DescriptionMarkdown string `json:"description_markdown,omitempty"` |
||||
Providers []*providerData `json:"providers"` |
||||
} |
||||
|
||||
type providerData struct { |
||||
Name string `json:"name"` |
||||
URL string `json:"url"` |
||||
Checksum string `json:"checksum"` |
||||
ChecksumType string `json:"checksum_type"` |
||||
} |
||||
|
||||
func packageDescriptorToMetadata(baseURL string, pd *packages_model.PackageDescriptor) *versionMetadata { |
||||
versionURL := baseURL + "/" + url.PathEscape(pd.Version.Version) |
||||
|
||||
providers := make([]*providerData, 0, len(pd.Files)) |
||||
|
||||
for _, f := range pd.Files { |
||||
providers = append(providers, &providerData{ |
||||
Name: f.Properties.GetByName(vagrant_module.PropertyProvider), |
||||
URL: versionURL + "/" + url.PathEscape(f.File.Name), |
||||
Checksum: f.Blob.HashSHA512, |
||||
ChecksumType: "sha512", |
||||
}) |
||||
} |
||||
|
||||
return &versionMetadata{ |
||||
Status: "active", |
||||
Version: pd.Version.Version, |
||||
Providers: providers, |
||||
} |
||||
} |
||||
|
||||
func EnumeratePackageVersions(ctx *context.Context) { |
||||
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeVagrant, ctx.Params("name")) |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
if len(pvs) == 0 { |
||||
apiError(ctx, http.StatusNotFound, err) |
||||
return |
||||
} |
||||
|
||||
pds, err := packages_model.GetPackageDescriptors(ctx, pvs) |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
|
||||
sort.Slice(pds, func(i, j int) bool { |
||||
return pds[i].SemVer.LessThan(pds[j].SemVer) |
||||
}) |
||||
|
||||
baseURL := fmt.Sprintf("%sapi/packages/%s/vagrant/%s", setting.AppURL, url.PathEscape(ctx.Package.Owner.Name), url.PathEscape(pds[0].Package.Name)) |
||||
|
||||
versions := make([]*versionMetadata, 0, len(pds)) |
||||
for _, pd := range pds { |
||||
versions = append(versions, packageDescriptorToMetadata(baseURL, pd)) |
||||
} |
||||
|
||||
ctx.JSON(http.StatusOK, &packageMetadata{ |
||||
Name: pds[0].Package.Name, |
||||
Description: pds[len(pds)-1].Metadata.(*vagrant_module.Metadata).Description, |
||||
Versions: versions, |
||||
}) |
||||
} |
||||
|
||||
func UploadPackageFile(ctx *context.Context) { |
||||
boxName := ctx.Params("name") |
||||
boxVersion := ctx.Params("version") |
||||
_, err := version.NewSemver(boxVersion) |
||||
if err != nil { |
||||
apiError(ctx, http.StatusBadRequest, err) |
||||
return |
||||
} |
||||
boxProvider := ctx.Params("provider") |
||||
if !strings.HasSuffix(boxProvider, ".box") { |
||||
apiError(ctx, http.StatusBadRequest, err) |
||||
return |
||||
} |
||||
|
||||
upload, needsClose, err := ctx.UploadStream() |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
if needsClose { |
||||
defer upload.Close() |
||||
} |
||||
|
||||
buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
defer buf.Close() |
||||
|
||||
metadata, err := vagrant_module.ParseMetadataFromBox(buf) |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
|
||||
if _, err := buf.Seek(0, io.SeekStart); err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
|
||||
_, _, err = packages_service.CreatePackageOrAddFileToExisting( |
||||
&packages_service.PackageCreationInfo{ |
||||
PackageInfo: packages_service.PackageInfo{ |
||||
Owner: ctx.Package.Owner, |
||||
PackageType: packages_model.TypeVagrant, |
||||
Name: boxName, |
||||
Version: boxVersion, |
||||
}, |
||||
SemverCompatible: true, |
||||
Creator: ctx.Doer, |
||||
Metadata: metadata, |
||||
}, |
||||
&packages_service.PackageFileCreationInfo{ |
||||
PackageFileInfo: packages_service.PackageFileInfo{ |
||||
Filename: strings.ToLower(boxProvider), |
||||
}, |
||||
Data: buf, |
||||
IsLead: true, |
||||
Properties: map[string]string{ |
||||
vagrant_module.PropertyProvider: strings.TrimSuffix(boxProvider, ".box"), |
||||
}, |
||||
}, |
||||
) |
||||
if err != nil { |
||||
if err == packages_model.ErrDuplicatePackageFile { |
||||
apiError(ctx, http.StatusConflict, err) |
||||
return |
||||
} |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
|
||||
ctx.Status(http.StatusCreated) |
||||
} |
||||
|
||||
func DownloadPackageFile(ctx *context.Context) { |
||||
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( |
||||
ctx, |
||||
&packages_service.PackageInfo{ |
||||
Owner: ctx.Package.Owner, |
||||
PackageType: packages_model.TypeVagrant, |
||||
Name: ctx.Params("name"), |
||||
Version: ctx.Params("version"), |
||||
}, |
||||
&packages_service.PackageFileInfo{ |
||||
Filename: ctx.Params("provider"), |
||||
}, |
||||
) |
||||
if err != nil { |
||||
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { |
||||
apiError(ctx, http.StatusNotFound, err) |
||||
return |
||||
} |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
defer s.Close() |
||||
|
||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime()) |
||||
} |
@ -0,0 +1,18 @@ |
||||
{{if eq .PackageDescriptor.Package.Type "vagrant"}} |
||||
<h4 class="ui top attached header">{{.locale.Tr "packages.installation"}}</h4> |
||||
<div class="ui attached segment"> |
||||
<div class="ui form"> |
||||
<div class="field"> |
||||
<label>{{svg "octicon-terminal"}} {{.locale.Tr "packages.vagrant.install"}}</label> |
||||
<div class="markup"><pre class="code-block"><code>vagrant box add --box-version {{.PackageDescriptor.Version.Version}} "{{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/vagrant/{{.PackageDescriptor.Package.Name}}"</code></pre></div> |
||||
</div> |
||||
<div class="field"> |
||||
<label>{{.locale.Tr "packages.vagrant.documentation" | Safe}}</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{{if .PackageDescriptor.Metadata.Description}} |
||||
<h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4> |
||||
<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div> |
||||
{{end}} |
||||
{{end}} |
@ -0,0 +1,5 @@ |
||||
{{if eq .PackageDescriptor.Package.Type "vagrant"}} |
||||
{{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{.locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}} |
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{.locale.Tr "packages.details.project_site"}}</a></div>{{end}} |
||||
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{.locale.Tr "packages.conan.details.repository"}}</a></div>{{end}} |
||||
{{end}} |
After Width: | Height: | Size: 814 B |
Loading…
Reference in new issue