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

Adding extension flag to summarize an extension buildpackage #344

Merged
merged 7 commits into from
Aug 28, 2024
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
39 changes: 33 additions & 6 deletions commands/summarize.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

type summarizeFlags struct {
buildpackTarballPath string
extensionTarballPath string
format string
}

Expand All @@ -19,16 +20,21 @@ func summarize() *cobra.Command {
Use: "summarize",
Short: "summarize buildpackage",
RunE: func(cmd *cobra.Command, args []string) error {
return summarizeRun(*flags)
isBuildpack, _ := cmd.Flags().GetString("buildpack")
if isBuildpack != "" {
return summarizeRun(*flags)
} else {
return summarizeExtensionRun(*flags)
}
},
}
cmd.Flags().StringVar(&flags.buildpackTarballPath, "buildpack", "", "path to a buildpackage tarball (required)")
cmd.Flags().StringVar(&flags.format, "format", "markdown", "format of output options are (markdown, json)")
cmd.Flags().StringVar(&flags.extensionTarballPath, "extension", "", "path to a buildpackage tarball (required)")
cmd.PersistentFlags().StringVar(&flags.format, "format", "markdown", "format of output options are (markdown, json)")

cmd.MarkFlagsOneRequired("buildpack", "extension")
cmd.MarkFlagsMutuallyExclusive("buildpack", "extension")

err := cmd.MarkFlagRequired("buildpack")
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to mark buildpack flag as required")
}
return cmd
}

Expand All @@ -55,3 +61,24 @@ func summarizeRun(flags summarizeFlags) error {

return nil
}

func summarizeExtensionRun(flags summarizeFlags) error {

extensionInspector := internal.NewExtensionInspector()
formatter := internal.NewExtensionFormatter(os.Stdout)
configs, err := extensionInspector.Dependencies(flags.extensionTarballPath)
if err != nil {
return fmt.Errorf("failed to inspect extension dependencies: %w", err)
}

switch flags.format {
case "markdown":
formatter.Markdown(configs)
case "json":
formatter.JSON(configs)
default:
return fmt.Errorf("unknown format %q, please choose from the following formats: markdown, json)", flags.format)
}

return nil
}
4 changes: 2 additions & 2 deletions integration/summarize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ version = "3.4.5"
})

context("failure cases", func() {
context("when the required buildpack flag is not set", func() {
context("when the required buildpack or extension flag is not set", func() {
it("prints an error message", func() {
command := exec.Command(
path, "summarize",
Expand All @@ -443,7 +443,7 @@ version = "3.4.5"
Expect(err).NotTo(HaveOccurred())
Eventually(session).Should(gexec.Exit(1), func() string { return buffer.String() })

Expect(session.Err.Contents()).To(ContainSubstring("Error: required flag(s) \"buildpack\" not set"))
Expect(session.Err.Contents()).To(ContainSubstring("Error: at least one of the flags in the group [buildpack extension] is required"))
})
})
})
Expand Down
115 changes: 115 additions & 0 deletions internal/extension_formatter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package internal

import (
"encoding/json"
"fmt"
"io"
"sort"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/paketo-buildpacks/packit/v2/cargo"
)

type ExtensionFormatter struct {
writer io.Writer
}

func NewExtensionFormatter(writer io.Writer) ExtensionFormatter {
return ExtensionFormatter{
writer: writer,
}
}

func printExtensionImplementation(writer io.Writer, config cargo.ExtensionConfig) {

if len(config.Metadata.DefaultVersions) > 0 {
fmt.Fprintf(writer, "#### Default Dependency Versions:\n| ID | Version |\n|---|---|\n")
var sortedDependencies []string
for key := range config.Metadata.DefaultVersions {
sortedDependencies = append(sortedDependencies, key)
}

sort.Strings(sortedDependencies)

for _, key := range sortedDependencies {
fmt.Fprintf(writer, "| %s | %s |\n", key, config.Metadata.DefaultVersions[key])
}
fmt.Fprintln(writer)
}

if len(config.Metadata.Dependencies) > 0 {
infoMap := map[depKey][]string{}
for _, d := range config.Metadata.Dependencies {

key := depKey{d.ID, d.Version, d.Source}
_, ok := infoMap[key]
if !ok {
sort.Strings(d.Stacks)
infoMap[key] = d.Stacks
} else {
val := infoMap[key]
val = append(val, d.Stacks...)
sort.Strings(val)
infoMap[key] = val
}
}

var sorted []cargo.ConfigExtensionMetadataDependency
for key, stacks := range infoMap {
sorted = append(sorted, cargo.ConfigExtensionMetadataDependency{
ID: key[0],
Version: key[1],
Stacks: stacks,
Source: key[2],
})
}

sort.Slice(sorted, func(i, j int) bool {
iVal := sorted[i]
jVal := sorted[j]

if iVal.ID == jVal.ID {
iVersion := semver.MustParse(iVal.Version)
jVersion := semver.MustParse(jVal.Version)

if iVersion.Equal(jVersion) {
iStacks := strings.Join(iVal.Stacks, " ")
jStacks := strings.Join(jVal.Stacks, " ")

return iStacks < jStacks
}

return iVersion.GreaterThan(jVersion)
}

return iVal.ID < jVal.ID
})

fmt.Fprintf(writer, "#### Dependencies:\n| Name | Version | Stacks | Source |\n|---|---|---|---|\n")
for _, d := range sorted {
fmt.Fprintf(writer, "| %s | %s | %s | %s |\n", d.ID, d.Version, strings.Join(d.Stacks, " "), d.Source)
}
fmt.Fprintln(writer)
}

}

func (f ExtensionFormatter) Markdown(entries []ExtensionMetadata) {

fmt.Fprintf(f.writer, "## %s %s\n", entries[0].Config.Extension.Name, entries[0].Config.Extension.Version)
fmt.Fprintf(f.writer, "\n**ID:** `%s`\n\n", entries[0].Config.Extension.ID)
fmt.Fprintf(f.writer, "**Digest:** `%s`\n\n", entries[0].SHA256)
printExtensionImplementation(f.writer, entries[0].Config)

}

func (f ExtensionFormatter) JSON(entries []ExtensionMetadata) {
var output struct {
Buildpackage cargo.ExtensionConfig `json:"buildpackage"`
}

output.Buildpackage = entries[0].Config

_ = json.NewEncoder(f.writer).Encode(&output)
}
203 changes: 203 additions & 0 deletions internal/extension_formatter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package internal_test

import (
"bytes"
"testing"

"github.com/paketo-buildpacks/jam/v2/internal"
"github.com/paketo-buildpacks/packit/v2/cargo"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
. "github.com/paketo-buildpacks/occam/matchers"
)

func testExtensionFormatter(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

buffer *bytes.Buffer
formatter internal.ExtensionFormatter
)

it.Before(func() {
buffer = bytes.NewBuffer(nil)
formatter = internal.NewExtensionFormatter(buffer)
})

context("Markdown", func() {
it("returns a list of dependencies", func() {
formatter.Markdown([]internal.ExtensionMetadata{
{
Config: cargo.ExtensionConfig{
Extension: cargo.ConfigExtension{
ID: "paketo-community/ubi-nodejs-extension",
Name: "Ubi Node.js Extension",
Version: "0.0.2",
},
Metadata: cargo.ConfigExtensionMetadata{
DefaultVersions: map[string]string{
"node": "20.*.*",
},
Dependencies: []cargo.ConfigExtensionMetadataDependency{
{
ID: "node",
Name: "Ubi Node Extension",
Source: "paketocommunity/run-nodejs-20-ubi-base",
Stacks: []string{"io.buildpacks.stacks.ubi8"},
Version: "20.1000",
},
{
ID: "node",
Name: "Ubi Node Extension",
Source: "paketocommunity/run-nodejs-18-ubi-base",
Stacks: []string{"io.buildpacks.stacks.ubi8"},
Version: "18.1000",
},
{
ID: "node",
Name: "Ubi Node Extension",
Source: "paketocommunity/run-nodejs-16-ubi-base",
Stacks: []string{"io.buildpacks.stacks.ubi8"},
Version: "16.1000",
},
},
},
},
SHA256: "sha256:manifest-sha",
},
})
Expect(buffer).To(ContainLines(
"## Ubi Node.js Extension 0.0.2",
"",
"**ID:** `paketo-community/ubi-nodejs-extension`",
"",
"**Digest:** `sha256:manifest-sha`",
"",
"#### Default Dependency Versions:",
"| ID | Version |",
"|---|---|",
"| node | 20.*.* |",
"",
"#### Dependencies:",
"| Name | Version | Stacks | Source |",
"|---|---|---|---|",
"| node | 20.1000 | io.buildpacks.stacks.ubi8 | paketocommunity/run-nodejs-20-ubi-base |",
"| node | 18.1000 | io.buildpacks.stacks.ubi8 | paketocommunity/run-nodejs-18-ubi-base |",
"| node | 16.1000 | io.buildpacks.stacks.ubi8 | paketocommunity/run-nodejs-16-ubi-base |",
))
})

context("when dependencies and default-versions are empty", func() {
it("returns a list of dependencies", func() {
formatter.Markdown([]internal.ExtensionMetadata{
{
Config: cargo.ExtensionConfig{
Extension: cargo.ConfigExtension{
ID: "paketo-community/ubi-nodejs-extension",
Name: "Ubi Node.js Extension",
Version: "0.0.2",
},
},
SHA256: "sha256:manifest-sha",
},
})
Expect(buffer.String()).To(Equal(`## Ubi Node.js Extension 0.0.2` +

"\n\n**ID:** `paketo-community/ubi-nodejs-extension`\n\n" +

"**Digest:** `sha256:manifest-sha`\n\n",
))
})
})

})

context("JSON", func() {
it("returns a list of dependencies", func() {
formatter.JSON([]internal.ExtensionMetadata{
{
Config: cargo.ExtensionConfig{
Extension: cargo.ConfigExtension{
ID: "paketo-community/ubi-nodejs-extension",
Name: "Ubi Node.js Extension",
Version: "0.0.2",
},
Metadata: cargo.ConfigExtensionMetadata{
DefaultVersions: map[string]string{
"node": "20.*.*",
},
Dependencies: []cargo.ConfigExtensionMetadataDependency{
{
ID: "node",
Name: "Ubi Node Extension",
Source: "paketocommunity/run-nodejs-20-ubi-base",
Stacks: []string{"io.buildpacks.stacks.ubi8"},
Version: "20.1000",
},
{
ID: "node",
Name: "Ubi Node Extension",
Source: "paketocommunity/run-nodejs-18-ubi-base",
Stacks: []string{"io.buildpacks.stacks.ubi8"},
Version: "18.1000",
},
{
ID: "node",
Name: "Ubi Node Extension",
Source: "paketocommunity/run-nodejs-16-ubi-base",
Stacks: []string{"io.buildpacks.stacks.ubi8"},
Version: "16.1000",
},
},
},
},
SHA256: "sha256:manifest-sha",
},
})
Expect(buffer.String()).To(MatchJSON(`{
"buildpackage": {
"extension": {
"id": "paketo-community/ubi-nodejs-extension",
"name": "Ubi Node.js Extension",
"version": "0.0.2"
},
"metadata": {
"default-versions": {
"node": "20.*.*"
},
"dependencies": [
{
"id": "node",
"name": "Ubi Node Extension",
"source": "paketocommunity/run-nodejs-20-ubi-base",
"stacks": [
"io.buildpacks.stacks.ubi8"
],
"version": "20.1000"
},
{
"id": "node",
"name": "Ubi Node Extension",
"source": "paketocommunity/run-nodejs-18-ubi-base",
"stacks": [
"io.buildpacks.stacks.ubi8"
],
"version": "18.1000"
},
{
"id": "node",
"name": "Ubi Node Extension",
"source": "paketocommunity/run-nodejs-16-ubi-base",
"stacks": [
"io.buildpacks.stacks.ubi8"
],
"version": "16.1000"
}
]
}
}
}`))
})
})
}
Loading
Loading