Skip to content

Make dedicated package for release archive handling #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 9 additions & 29 deletions internal/command/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ import (
"github.com/arduino/libraries-repository-engine/internal/configuration"
"github.com/arduino/libraries-repository-engine/internal/feedback"
"github.com/arduino/libraries-repository-engine/internal/libraries"
"github.com/arduino/libraries-repository-engine/internal/libraries/archive"
"github.com/arduino/libraries-repository-engine/internal/libraries/db"
"github.com/arduino/libraries-repository-engine/internal/libraries/gitutils"
"github.com/arduino/libraries-repository-engine/internal/libraries/hash"
"github.com/go-git/go-git/v5/plumbing"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -268,23 +268,19 @@ func syncLibraryTaggedRelease(logger *log.Logger, repo *libraries.Repository, ta
releaseLog += formattedReport
}

zipName := libraries.ZipFolderName(library)
lib := filepath.Base(filepath.Clean(filepath.Join(repo.FolderPath, "..")))
host := filepath.Base(filepath.Clean(filepath.Join(repo.FolderPath, "..", "..")))
zipFilePath, err := libraries.ZipRepo(repo.FolderPath, filepath.Join(config.LibrariesFolder, host, lib), zipName)
archiveData, err := archive.New(repo, library, config)
if err != nil {
return fmt.Errorf("Error while configuring library release archive: %s", err)
}
if err := archiveData.Create(); err != nil {
return fmt.Errorf("Error while zipping library: %s", err)
}

size, checksum, err := getSizeAndCalculateChecksum(zipFilePath)
if err != nil {
return fmt.Errorf("Error while calculating checksums: %s", err)
}
release := db.FromLibraryToRelease(library)
release.URL = config.BaseDownloadURL + host + "/" + lib + "/" + zipName + ".zip"
release.ArchiveFileName = zipName + ".zip"
release.Size = size
release.Checksum = checksum
release.URL = archiveData.URL
release.ArchiveFileName = archiveData.FileName
release.Size = archiveData.Size
release.Checksum = archiveData.Checksum
release.Log = releaseLog

if err := libraries.UpdateLibrary(release, repo.URL, libraryDb); err != nil {
Expand All @@ -294,22 +290,6 @@ func syncLibraryTaggedRelease(logger *log.Logger, repo *libraries.Repository, ta
return nil
}

func getSizeAndCalculateChecksum(filePath string) (int64, string, error) {
info, err := os.Stat(filePath)
if err != nil {
return -1, "", err
}

size := info.Size()

checksum, err := hash.Checksum(filePath)
if err != nil {
return -1, "", err
}

return size, checksum, nil
}

func outputLogFile(logger *log.Logger, repoMetadata *libraries.Repo, buffer *bytes.Buffer) error {
if config.LogsFolder == "" {
return nil
Expand Down
119 changes: 119 additions & 0 deletions internal/libraries/archive/archive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// This file is part of libraries-repository-engine.
//
// Copyright 2021 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

// Package archive handles the library release archive.
package archive

import (
"net/url"
"os"
"path/filepath"
"regexp"

"github.com/arduino/libraries-repository-engine/internal/configuration"
"github.com/arduino/libraries-repository-engine/internal/libraries"
"github.com/arduino/libraries-repository-engine/internal/libraries/hash"
"github.com/arduino/libraries-repository-engine/internal/libraries/metadata"
"github.com/arduino/libraries-repository-engine/internal/libraries/zip"
)

// Archive is the type for library release archive data.
type Archive struct {
SourcePath string
RootName string // Name of the root folder inside the archive.
FileName string
Path string // Full path of the archive.
URL string // URL the archive will have on the download server.
Size int64
Checksum string
}

// New initializes and returns an Archive object.
func New(repository *libraries.Repository, libraryMetadata *metadata.LibraryMetadata, config *configuration.Config) (*Archive, error) {
repositoryURLData, err := url.Parse(repository.URL)
if err != nil {
return nil, err
}
// e.g., https://github.com/arduino-libraries/Servo.git -> github.com
repositoryHost := repositoryURLData.Host
// e.g., https://github.com/arduino-libraries/Servo.git -> arduino-libraries
repositoryParent := filepath.Base(filepath.Dir(repositoryURLData.Path))

// Unlike the other path components, the filename is based on library name, not repository name URL.
fileName := zipFolderName(libraryMetadata) + ".zip"

return &Archive{
SourcePath: repository.FolderPath,
RootName: zipFolderName(libraryMetadata),
FileName: fileName,
Path: filepath.Join(config.LibrariesFolder, repositoryHost, repositoryParent, fileName),
URL: config.BaseDownloadURL + repositoryHost + "/" + repositoryParent + "/" + fileName,
}, nil
}

// Create makes an archive file according to the data of the Archive object and updates the object with the size and
// checksum for the resulting file.
func (archive *Archive) Create() error {
err := os.MkdirAll(filepath.Dir(archive.Path), os.FileMode(0755))
if err != nil {
return err
}

if err := zip.Directory(archive.SourcePath, archive.RootName, archive.Path); err != nil {
os.Remove(archive.Path)
return err
}

size, checksum, err := getSizeAndCalculateChecksum(archive.Path)
if err != nil {
return err
}
archive.Size = size
archive.Checksum = checksum

return nil
}

var zipFolderNamePattern = regexp.MustCompile("[^a-zA-Z0-9]")

// zipFolderName returns the name to use for the folder.
func zipFolderName(library *metadata.LibraryMetadata) string {
return zipFolderNamePattern.ReplaceAllString(library.Name, "_") + "-" + library.Version
}

// getSizeAndCalculateChecksum returns the size and SHA-256 checksum for the given file.
func getSizeAndCalculateChecksum(filePath string) (int64, string, error) {
info, err := os.Stat(filePath)
if err != nil {
return -1, "", err
}

size := info.Size()

checksum, err := hash.Checksum(filePath)
if err != nil {
return -1, "", err
}

return size, checksum, nil
}
88 changes: 88 additions & 0 deletions internal/libraries/archive/archive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// This file is part of libraries-repository-engine.
//
// Copyright 2021 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package archive

import (
"os"
"path/filepath"
"testing"

"github.com/arduino/libraries-repository-engine/internal/configuration"
"github.com/arduino/libraries-repository-engine/internal/libraries"
"github.com/arduino/libraries-repository-engine/internal/libraries/metadata"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var testDataPath string

func init() {
workingDirectory, err := os.Getwd()
if err != nil {
panic(err)
}
testDataPath = filepath.Join(workingDirectory, "testdata")
}

func TestNew(t *testing.T) {
repository := libraries.Repository{
FolderPath: "/qux/repos/some-repo",
URL: "https://github.com/Foo/Bar.git",
}
libraryMetadata := metadata.LibraryMetadata{
Name: "Foo Bar",
Version: "1.2.3",
}
config := configuration.Config{
LibrariesFolder: "/baz/libs/",
BaseDownloadURL: "https://example/com/libraries/",
}

archiveObject, err := New(&repository, &libraryMetadata, &config)
require.NoError(t, err)
assert.Equal(t, "/qux/repos/some-repo", archiveObject.SourcePath)
assert.Equal(t, "Foo_Bar-1.2.3", archiveObject.RootName)
assert.Equal(t, "Foo_Bar-1.2.3.zip", archiveObject.FileName)
assert.Equal(t, filepath.Join("/baz/libs/github.com/Foo/Foo_Bar-1.2.3.zip"), archiveObject.Path)
assert.Equal(t, "https://example/com/libraries/github.com/Foo/Foo_Bar-1.2.3.zip", archiveObject.URL)
}

func TestCreate(t *testing.T) {
archiveDir := filepath.Join(os.TempDir(), "TestCreateArchiveDir")
defer os.RemoveAll(archiveDir)
archivePath := filepath.Join(archiveDir, "TestCreateArchive.zip")

archiveObject := Archive{
Path: archivePath,
SourcePath: filepath.Join(testDataPath, "gitclones", "SomeRepository"),
RootName: "SomeLibrary",
}

err := archiveObject.Create()
require.NoError(t, err, "This test must be run as administrator on Windows to have symlink creation privilege.")

assert.FileExists(t, archivePath)
assert.Greater(t, archiveObject.Size, int64(0))
assert.NotEmpty(t, archiveObject.Checksum)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This is some arbitrary source content to stand in for a library release. It is not intended to be a Git repository
because that is not convenient to have in the repository and not relevant to testing this package.
21 changes: 13 additions & 8 deletions internal/libraries/git_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,24 @@
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package libraries
package libraries_test

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/arduino/libraries-repository-engine/internal/configuration"
"github.com/arduino/libraries-repository-engine/internal/libraries"
"github.com/arduino/libraries-repository-engine/internal/libraries/archive"
"github.com/arduino/libraries-repository-engine/internal/libraries/db"
"github.com/arduino/libraries-repository-engine/internal/libraries/gitutils"
"github.com/stretchr/testify/require"
)

func TestUpdateLibraryJson(t *testing.T) {
repos, err := ListRepos("./testdata/git_test_repo.txt")
repos, err := libraries.ListRepos("./testdata/git_test_repo.txt")

require.NoError(t, err)
require.NotNil(t, repos)
Expand All @@ -51,7 +54,7 @@ func TestUpdateLibraryJson(t *testing.T) {
subfolder, err := repo.AsFolder()
require.NoError(t, err)

r, err := CloneOrFetch(repo, filepath.Join("/tmp", subfolder))
r, err := libraries.CloneOrFetch(repo, filepath.Join("/tmp", subfolder))
require.NoError(t, err)
require.NotNil(t, r)

Expand All @@ -64,19 +67,21 @@ func TestUpdateLibraryJson(t *testing.T) {

err = gitutils.CheckoutTag(r.Repository, tag)

library, err := GenerateLibraryFromRepo(r)
library, err := libraries.GenerateLibraryFromRepo(r)
require.NoError(t, err)
require.NotNil(t, library)

zipFolderName := ZipFolderName(library)
config := configuration.Config{LibrariesFolder: librariesRepo}
archiveData, err := archive.New(r, library, &config)
require.NoError(t, err)

release := db.FromLibraryToRelease(library)

zipFilePath, err := ZipRepo(r.FolderPath, librariesRepo, zipFolderName)
err = archiveData.Create()
require.NoError(t, err)
require.NotEmpty(t, zipFilePath)
require.NotEmpty(t, archiveData.Path)

err = UpdateLibrary(release, r.URL, libraryDb)
err = libraries.UpdateLibrary(release, r.URL, libraryDb)
require.NoError(t, err)

}
Expand Down
54 changes: 0 additions & 54 deletions internal/libraries/repoarchive.go

This file was deleted.