Skip to content
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

Docker - Skip build-info if there are no image layers in Artifactory #722

Merged
merged 3 commits into from
Mar 28, 2023
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
50 changes: 41 additions & 9 deletions artifactory/utils/container/buildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ func (builder *buildInfoBuilder) createPushBuildProperties(imageManifest *manife
// Add artifacts.
artifacts = append(artifacts, getManifestArtifact(candidateLayers["manifest.json"]))
artifacts = append(artifacts, candidateLayers[digestToLayer(builder.imageSha2)].ToArtifact())

// Add layers.
imageLayers = append(imageLayers, *candidateLayers["manifest.json"])
imageLayers = append(imageLayers, *candidateLayers[digestToLayer(builder.imageSha2)])
Expand All @@ -392,17 +393,19 @@ func (builder *buildInfoBuilder) createPushBuildProperties(imageManifest *manife
if err != nil {
return nil, nil, nil, err
}

// Add image layers as artifacts and dependencies.
for i := 0; i < totalLayers; i++ {
layerFileName := digestToLayer(imageManifest.Layers[i].Digest)
item, layerExists := candidateLayers[layerFileName]
if !layerExists {
err := handleMissingLayer(imageManifest.Layers[i].MediaType, layerFileName)
err := handleForeignLayer(imageManifest.Layers[i].MediaType, layerFileName)
if err != nil {
return nil, nil, nil, err
}
continue
}

// Decide if the layer is also a dependency.
if i < totalDependencies {
dependencies = append(dependencies, item.ToDependency())
Expand All @@ -413,23 +416,52 @@ func (builder *buildInfoBuilder) createPushBuildProperties(imageManifest *manife
return
}

func (builder *buildInfoBuilder) createPullBuildProperties(imageManifest *manifest, candidateLayers map[string]*utils.ResultItem) (dependencies []buildinfo.Dependency, err error) {
// Add dependencies.
dependencies = append(dependencies, getManifestDependency(candidateLayers["manifest.json"]))
dependencies = append(dependencies, candidateLayers[digestToLayer(builder.imageSha2)].ToDependency())
// Add image layers as dependencies.
func (builder *buildInfoBuilder) createPullBuildProperties(imageManifest *manifest, imageLayers map[string]*utils.ResultItem) ([]buildinfo.Dependency, error) {
configDependencies, err := getDependenciesFromManifestConfig(imageLayers, builder.imageSha2)
if err != nil {
log.Debug(err.Error())
return nil, nil
}

layerDependencies, err := getDependenciesFromManifestLayer(imageLayers, imageManifest)
if err != nil {
log.Debug(err.Error())
return nil, nil
}

return append(configDependencies, layerDependencies...), nil
}

func getDependenciesFromManifestConfig(candidateLayers map[string]*utils.ResultItem, imageSha2 string) ([]buildinfo.Dependency, error) {
var dependencies []buildinfo.Dependency
manifestSearchResults, found := candidateLayers["manifest.json"]
if !found {
return nil, errorutils.CheckErrorf("failed to collect build-info. The manifest.json was not found in Artifactory")
}

dependencies = append(dependencies, getManifestDependency(manifestSearchResults))
imageDetails, found := candidateLayers[digestToLayer(imageSha2)]
if !found {
return nil, errorutils.CheckErrorf("failed to collect build-info. Image '" + imageSha2 + "' was not found in Artifactory")
}

return append(dependencies, imageDetails.ToDependency()), nil
}

func getDependenciesFromManifestLayer(layers map[string]*utils.ResultItem, imageManifest *manifest) ([]buildinfo.Dependency, error) {
var dependencies []buildinfo.Dependency
for i := 0; i < len(imageManifest.Layers); i++ {
layerFileName := digestToLayer(imageManifest.Layers[i].Digest)
item, layerExists := candidateLayers[layerFileName]
item, layerExists := layers[layerFileName]
if !layerExists {
if err := handleMissingLayer(imageManifest.Layers[i].MediaType, layerFileName); err != nil {
if err := handleForeignLayer(imageManifest.Layers[i].MediaType, layerFileName); err != nil {
return nil, err
}
continue
}
dependencies = append(dependencies, item.ToDependency())
}
return
return dependencies, nil
}

func (builder *buildInfoBuilder) totalDependencies(image *utils.ResultItem) (int, error) {
Expand Down
119 changes: 119 additions & 0 deletions artifactory/utils/container/buildinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"path/filepath"
"testing"

"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
"github.com/stretchr/testify/assert"
)

Expand All @@ -14,6 +15,7 @@ func TestGetManifestPaths(t *testing.T) {
for i, result := range results {
assert.Equal(t, expected[i], result)
}

results = getManifestPaths("/hello-world/latest", "docker-local", Pull)
assert.Len(t, results, 4)
expected = append(expected, "docker-local/library/hello-world/latest/*", "docker-local/library/latest/*")
Expand All @@ -27,6 +29,7 @@ func TestGetManifestPaths(t *testing.T) {
for i, result := range results {
assert.Equal(t, expected[i], result)
}

results = getManifestPaths("/docker-remote/hello-world/latest", "docker-remote", Pull)
assert.Len(t, results, 4)
expected = append(expected, "docker-remote/library/docker-remote/hello-world/latest/*", "docker-remote/library/hello-world/latest/*")
Expand All @@ -42,3 +45,119 @@ func TestGetImageWithDigest(t *testing.T) {
assert.Equal(t, "my-image-tag", tag.name)
assert.Equal(t, "sha256:12345", sha256)
}

var dummySearchResults = &utils.ResultItem{
Type: "json",
Actual_Md5: "md5",
Actual_Sha1: "sha1",
Sha256: "sha2",
Size: 1,
}

func TestManifestConfig(t *testing.T) {
dependencies, err := getDependenciesFromManifestConfig(createManifestConfig())
assert.NoError(t, err)
assert.Len(t, dependencies, 2)
}

func createManifestConfig() (map[string]*utils.ResultItem, string) {
config := make(map[string]*utils.ResultItem, 0)
config["manifest.json"] = dummySearchResults
config["sha__123"] = dummySearchResults
return config, "sha:123"
}

func TestManifestConfigNoManifestFound(t *testing.T) {
_, err := getDependenciesFromManifestConfig(createEmptyManifestConfig())
assert.ErrorContains(t, err, "The manifest.json was not found in Artifactory")
}

func createEmptyManifestConfig() (map[string]*utils.ResultItem, string) {
config := make(map[string]*utils.ResultItem, 0)
return config, "sha:123"
}

func TestManifestConfigNoLayer(t *testing.T) {
_, err := getDependenciesFromManifestConfig(createManifestConfigWithNoLayer())
assert.ErrorContains(t, err, "Image 'sha:123' was not found in Artifactory")
}

func createManifestConfigWithNoLayer() (map[string]*utils.ResultItem, string) {
config := make(map[string]*utils.ResultItem, 0)
config["manifest.json"] = dummySearchResults
return config, "sha:123"
}

func TestGetDependenciesFromManifestLayer(t *testing.T) {
searchResults, manifest := createManifestConfigWithLayer()
dependencies, err := getDependenciesFromManifestLayer(searchResults, manifest)
assert.NoError(t, err)
assert.Len(t, dependencies, 1)
}

func createManifestConfigWithLayer() (map[string]*utils.ResultItem, *manifest) {
manifest := &manifest{
Layers: []layer{{
Digest: "sha:1",
MediaType: "MediaType",
}},
}
searchResults := make(map[string]*utils.ResultItem, 0)
searchResults["manifest.json"] = dummySearchResults
searchResults["sha__1"] = dummySearchResults
searchResults["sha__2"] = dummySearchResults
return searchResults, manifest
}

func TestMissingDependenciesInManifestLayer(t *testing.T) {
searchResults, manifest := createManifestConfigWithMissingLayer()
_, err := getDependenciesFromManifestLayer(searchResults, manifest)
assert.ErrorContains(t, err, "Could not find layer: sha__2 in Artifactory")
}

func createManifestConfigWithMissingLayer() (map[string]*utils.ResultItem, *manifest) {
manifest := &manifest{
Layers: []layer{
{
Digest: "sha:1",
MediaType: "MediaType",
},
// Missing layer
{
Digest: "sha:2",
MediaType: "type",
},
},
}
searchResults := make(map[string]*utils.ResultItem, 0)
searchResults["manifest.json"] = dummySearchResults
searchResults["sha__1"] = dummySearchResults
return searchResults, manifest
}

func TestForeignDependenciesInManifestLayer(t *testing.T) {
searchResults, manifest := createManifestConfigWithForeignLayer()
dependencies, err := getDependenciesFromManifestLayer(searchResults, manifest)
assert.NoError(t, err)
assert.Len(t, dependencies, 1)
}

func createManifestConfigWithForeignLayer() (map[string]*utils.ResultItem, *manifest) {
manifest := &manifest{
Layers: []layer{
{
Digest: "sha:1",
MediaType: "MediaType",
},
// Foreign layer
{
Digest: "sha:2",
MediaType: foreignLayerMediaType,
},
},
}
searchResults := make(map[string]*utils.ResultItem, 0)
searchResults["manifest.json"] = dummySearchResults
searchResults["sha__1"] = dummySearchResults
return searchResults, manifest
}
7 changes: 4 additions & 3 deletions artifactory/utils/container/localagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ func (labib *localAgentbuildInfoBuilder) Build(module string) (*buildinfo.BuildI
// Search for image build-info.
candidateLayers, manifest, err := labib.searchImage()
if err != nil {
log.Warn("Failed to collect build-info, couldn't find image '"+labib.buildInfoBuilder.image.name+"' in Artifactory. Error:", err.Error())
log.Warn("Failed to collect build-info. No layer(s) was found for image:'" + labib.buildInfoBuilder.image.name + "'. Hint, try to delete the image from the local cache and rerun the command")
log.Debug(err.Error())
return nil, nil
} else {
log.Debug("Found manifest.json. Proceeding to create build-info.")
log.Debug("Found manifest.json with the following layers to create build-info:", candidateLayers)
}
// Create build-info from search results.
return labib.buildInfoBuilder.createBuildInfo(labib.commandType, manifest, candidateLayers, module)
Expand Down Expand Up @@ -185,7 +186,7 @@ func downloadMarkerLayersToRemoteCache(resultMap map[string]*utils.ResultItem, b
return totalDownloaded, nil
}

func handleMissingLayer(layerMediaType, layerFileName string) error {
func handleForeignLayer(layerMediaType, layerFileName string) error {
// Allow missing layer to be of a foreign type.
if layerMediaType == foreignLayerMediaType {
log.Info(fmt.Sprintf("Foreign layer: %s is missing in Artifactory and therefore will not be added to the build-info.", layerFileName))
Expand Down