mirror of
https://github.com/anchore/syft.git
synced 2026-11-03 16:06:36 +01:00
Add cataloger for Swift Package Manager. (#1919)
Signed-off-by: Tristan Farkas <Tristan.Farkas@axis.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Package swift provides a concrete Cataloger implementation for Podfile.lock files.
|
||||
Package swift provides a concrete Cataloger implementation for Podfile.lock and Package.resolved files.
|
||||
*/
|
||||
package swift
|
||||
|
||||
@@ -7,6 +7,11 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
func NewSwiftPackageManagerCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger("spm-cataloger").
|
||||
WithParserByGlobs(parsePackageResolved, "**/Package.resolved", "**/.package.resolved")
|
||||
}
|
||||
|
||||
// NewCocoapodsCataloger returns a new Swift Cocoapods lock file cataloger object.
|
||||
func NewCocoapodsCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger("cocoapods-cataloger").
|
||||
|
||||
@@ -1,16 +1,37 @@
|
||||
package swift
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func newPackage(name, version, hash string, locations ...file.Location) pkg.Package {
|
||||
func newSwiftPackageManagerPackage(name, version, sourceURL, revision string, locations ...file.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
PURL: packageURL(name, version),
|
||||
PURL: swiftPackageManagerPackageURL(name, version, sourceURL),
|
||||
Locations: file.NewLocationSet(locations...),
|
||||
Type: pkg.SwiftPkg,
|
||||
Language: pkg.Swift,
|
||||
MetadataType: pkg.SwiftPackageManagerMetadataType,
|
||||
Metadata: pkg.SwiftPackageManagerMetadata{
|
||||
Revision: revision,
|
||||
},
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func newCocoaPodsPackage(name, version, hash string, locations ...file.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
PURL: cocoaPodsPackageURL(name, version),
|
||||
Locations: file.NewLocationSet(locations...),
|
||||
Type: pkg.CocoapodsPkg,
|
||||
Language: pkg.Swift,
|
||||
@@ -25,7 +46,7 @@ func newPackage(name, version, hash string, locations ...file.Location) pkg.Pack
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(name, version string) string {
|
||||
func cocoaPodsPackageURL(name, version string) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
@@ -37,3 +58,16 @@ func packageURL(name, version string) string {
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func swiftPackageManagerPackageURL(name, version, sourceURL string) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeSwift,
|
||||
strings.Replace(sourceURL, "https://", "", 1),
|
||||
name,
|
||||
version,
|
||||
qualifiers,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_packageURL(t *testing.T) {
|
||||
func Test_cocoaPodsPackageURL(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
version string
|
||||
@@ -27,7 +27,7 @@ func Test_packageURL(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, packageURL(tt.args.name, tt.args.version))
|
||||
assert.Equal(t, tt.want, cocoaPodsPackageURL(tt.args.name, tt.args.version))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package swift
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
var _ generic.Parser = parsePackageResolved
|
||||
|
||||
// swift package manager has two versions (1 and 2) of the resolved files, the types below describes the serialization strategies for each version
|
||||
// with its suffix indicating which version its specific to.
|
||||
|
||||
type packageResolvedV1 struct {
|
||||
PackageObject packageObjectV1 `json:"object"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type packageObjectV1 struct {
|
||||
Pins []packagePinsV1
|
||||
}
|
||||
|
||||
type packagePinsV1 struct {
|
||||
Name string `json:"package"`
|
||||
RepositoryURL string `json:"repositoryURL"`
|
||||
State packageState `json:"state"`
|
||||
}
|
||||
|
||||
type packageResolvedV2 struct {
|
||||
Pins []packagePinsV2
|
||||
}
|
||||
|
||||
type packagePinsV2 struct {
|
||||
Identity string `json:"identity"`
|
||||
Kind string `json:"kind"`
|
||||
Location string `json:"location"`
|
||||
State packageState `json:"state"`
|
||||
}
|
||||
|
||||
type packagePin struct {
|
||||
Identity string
|
||||
Location string
|
||||
Revision string
|
||||
Version string
|
||||
}
|
||||
|
||||
type packageState struct {
|
||||
Revision string `json:"revision"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// parsePackageResolved is a parser for the contents of a Package.resolved file, which is generated by Xcode after it's resolved Swift Package Manger packages.
|
||||
func parsePackageResolved(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
dec := json.NewDecoder(reader)
|
||||
var packageResolvedData map[string]interface{}
|
||||
for {
|
||||
if err := dec.Decode(&packageResolvedData); errors.Is(err, io.EOF) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse Package.resolved file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var pins, err = pinsForVersion(packageResolvedData, packageResolvedData["version"].(float64))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, packagePin := range pins {
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newSwiftPackageManagerPackage(
|
||||
packagePin.Identity,
|
||||
packagePin.Version,
|
||||
packagePin.Location,
|
||||
packagePin.Revision,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func pinsForVersion(data map[string]interface{}, version float64) ([]packagePin, error) {
|
||||
var genericPins []packagePin
|
||||
switch version {
|
||||
case 1:
|
||||
t := packageResolvedV1{}
|
||||
jsonString, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parseErr := json.Unmarshal(jsonString, &t)
|
||||
if parseErr != nil {
|
||||
return nil, parseErr
|
||||
}
|
||||
for _, pin := range t.PackageObject.Pins {
|
||||
genericPins = append(genericPins, packagePin{
|
||||
pin.Name,
|
||||
pin.RepositoryURL,
|
||||
pin.State.Revision,
|
||||
pin.State.Version,
|
||||
})
|
||||
}
|
||||
case 2:
|
||||
t := packageResolvedV2{}
|
||||
jsonString, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parseErr := json.Unmarshal(jsonString, &t)
|
||||
if parseErr != nil {
|
||||
return nil, parseErr
|
||||
}
|
||||
for _, pin := range t.Pins {
|
||||
genericPins = append(genericPins, packagePin{
|
||||
pin.Identity,
|
||||
pin.Location,
|
||||
pin.State.Revision,
|
||||
pin.State.Version,
|
||||
})
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown swift package manager version, %f", version)
|
||||
}
|
||||
return genericPins, nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package swift
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func TestParsePackageResolved(t *testing.T) {
|
||||
fixture := "test-fixtures/Package.resolved"
|
||||
locations := file.NewLocationSet(file.NewLocation(fixture))
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "swift-algorithms",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:swift/github.com/apple/swift-algorithms.git/swift-algorithms@1.0.0",
|
||||
Locations: locations,
|
||||
Language: pkg.Swift,
|
||||
Type: pkg.SwiftPkg,
|
||||
MetadataType: pkg.SwiftPackageManagerMetadataType,
|
||||
Metadata: pkg.SwiftPackageManagerMetadata{
|
||||
Revision: "b14b7f4c528c942f121c8b860b9410b2bf57825e",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "swift-async-algorithms",
|
||||
Version: "0.1.0",
|
||||
PURL: "pkg:swift/github.com/apple/swift-async-algorithms.git/swift-async-algorithms@0.1.0",
|
||||
Locations: locations,
|
||||
Language: pkg.Swift,
|
||||
Type: pkg.SwiftPkg,
|
||||
MetadataType: pkg.SwiftPackageManagerMetadataType,
|
||||
Metadata: pkg.SwiftPackageManagerMetadata{
|
||||
Revision: "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "swift-atomics",
|
||||
Version: "1.1.0",
|
||||
PURL: "pkg:swift/github.com/apple/swift-atomics.git/swift-atomics@1.1.0",
|
||||
Locations: locations,
|
||||
Language: pkg.Swift,
|
||||
Type: pkg.SwiftPkg,
|
||||
MetadataType: pkg.SwiftPackageManagerMetadataType,
|
||||
Metadata: pkg.SwiftPackageManagerMetadata{
|
||||
Revision: "6c89474e62719ddcc1e9614989fff2f68208fe10",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "swift-collections",
|
||||
Version: "1.0.4",
|
||||
PURL: "pkg:swift/github.com/apple/swift-collections.git/swift-collections@1.0.4",
|
||||
Locations: locations,
|
||||
Language: pkg.Swift,
|
||||
Type: pkg.SwiftPkg,
|
||||
MetadataType: pkg.SwiftPackageManagerMetadataType,
|
||||
Metadata: pkg.SwiftPackageManagerMetadata{
|
||||
Revision: "937e904258d22af6e447a0b72c0bc67583ef64a2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "swift-numerics",
|
||||
Version: "1.0.2",
|
||||
PURL: "pkg:swift/github.com/apple/swift-numerics/swift-numerics@1.0.2",
|
||||
Locations: locations,
|
||||
Language: pkg.Swift,
|
||||
Type: pkg.SwiftPkg,
|
||||
MetadataType: pkg.SwiftPackageManagerMetadataType,
|
||||
Metadata: pkg.SwiftPackageManagerMetadata{
|
||||
Revision: "0a5bc04095a675662cf24757cc0640aa2204253b",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: no relationships are under test yet
|
||||
var expectedRelationships []artifact.Relationship
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePackageResolved, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func parsePodfileLock(_ file.Resolver, _ *generic.Environment, reader file.Locat
|
||||
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackage(
|
||||
newCocoaPodsPackage(
|
||||
podName,
|
||||
podVersion,
|
||||
pkgHash,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-algorithms",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-algorithms.git",
|
||||
"state" : {
|
||||
"revision" : "b14b7f4c528c942f121c8b860b9410b2bf57825e",
|
||||
"version" : "1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-async-algorithms",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-async-algorithms.git",
|
||||
"state" : {
|
||||
"revision" : "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a",
|
||||
"version" : "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-atomics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-atomics.git",
|
||||
"state" : {
|
||||
"revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10",
|
||||
"version" : "1.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections.git",
|
||||
"state" : {
|
||||
"revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
|
||||
"version" : "1.0.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-numerics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-numerics",
|
||||
"state" : {
|
||||
"revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
|
||||
"version" : "1.0.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
Reference in New Issue
Block a user