Skip to content

Commit

Permalink
implement SOLID principles for folder output adapter
Browse files Browse the repository at this point in the history
Signed-off-by: Vivek Kumar Sahu <[email protected]>
  • Loading branch information
viveksahu26 committed Feb 20, 2025
1 parent c939e1f commit e3617ca
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 188 deletions.
4 changes: 2 additions & 2 deletions pkg/adapter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func NewAdapter(ctx *tcontext.TransferMetadata, config types.Config) (map[types.
adapters[types.InputAdapterRole] = &github.GitHubAdapter{Role: types.InputAdapterRole}

case types.FolderAdapterType:
adapters[types.InputAdapterRole] = &ifolder.FolderAdapter{Role: types.InputAdapterRole}
adapters[types.InputAdapterRole] = &ifolder.FolderAdapter{Role: types.InputAdapterRole, Fetcher: &ifolder.SequentialFetcher{}}

case types.InterlynkAdapterType:
adapters[types.InputAdapterRole] = &interlynk.InterlynkAdapter{Role: types.InputAdapterRole}
Expand All @@ -79,7 +79,7 @@ func NewAdapter(ctx *tcontext.TransferMetadata, config types.Config) (map[types.
switch types.AdapterType(config.DestinationType) {

case types.FolderAdapterType:
adapters[types.OutputAdapterRole] = &ofolder.FolderAdapter{Role: types.OutputAdapterRole}
adapters[types.OutputAdapterRole] = &ofolder.FolderAdapter{Role: types.OutputAdapterRole, Uploader: &ofolder.SequentialUploader{}}

case types.InterlynkAdapterType:
adapters[types.OutputAdapterRole] = &interlynk.InterlynkAdapter{Role: types.OutputAdapterRole}
Expand Down
84 changes: 11 additions & 73 deletions pkg/source/folder/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,9 @@ import (

// FolderAdapter handles fetching SBOMs from folders
type FolderAdapter struct {
config *FolderConfig
Role types.AdapterRole // "input" or "output" adapter type

fetcher SBOMFetcher
}

func NewFolderAdapter(config *FolderConfig) *FolderAdapter {
fetcher, ok := fetcherFactory[config.ProcessingMode]
if !ok {
fetcher = fetcherFactory[types.FetchSequential]
}
return &FolderAdapter{config: config, fetcher: fetcher}
config *FolderConfig
Role types.AdapterRole // "input" or "output" adapter type
Fetcher SBOMFetcher
}

// AddCommandParams adds Folder-specific CLI flags
Expand Down Expand Up @@ -97,17 +88,21 @@ func (f *FolderAdapter) ParseAndValidateParams(cmd *cobra.Command) error {
return fmt.Errorf("invalid input adapter flag usage:\n %s\n\nUse 'sbommv transfer --help' for correct usage.", strings.Join(invalidFlags, "\n "))
}

f.config.FolderPath = folderPath
f.config.Recursive = folderRecurse
f.config.ProcessingMode = types.ProcessingMode(mode)
cfg := FolderConfig{
FolderPath: folderPath,
Recursive: folderRecurse,
ProcessingMode: types.ProcessingMode(mode),
}

f.config = &cfg

return nil
}

// FetchSBOMs initializes the Folder SBOM iterator using the unified method
func (f *FolderAdapter) FetchSBOMs(ctx *tcontext.TransferMetadata) (iterator.SBOMIterator, error) {
logger.LogDebug(ctx.Context, "Initializing SBOM fetching", "mode", f.config.ProcessingMode)
return f.fetcher.Fetch(ctx, f.config)
return f.Fetcher.Fetch(ctx, f.config)
}

// OutputSBOMs should return an error since Folder does not support SBOM uploads
Expand All @@ -120,60 +115,3 @@ func (f *FolderAdapter) DryRun(ctx *tcontext.TransferMetadata, iter iterator.SBO
reporter := NewFolderReporter(false, "")
return reporter.DryRun(ctx.Context, iter)
}

// DryRun for Folder Adapter: Displays all fetched SBOMs from folder adapter
// func (f *FolderAdapter) DryRun(ctx *tcontext.TransferMetadata, iterator iterator.SBOMIterator) error {
// logger.LogDebug(ctx.Context, "Dry-run mode: Displaying SBOMs fetched from folder input adapter")

// var outputDir string
// var verbose bool

// processor := sbom.NewSBOMProcessor(outputDir, verbose)
// sbomCount := 0
// fmt.Println()
// fmt.Printf("📦 Details of all Fetched SBOMs by Folder Input Adapter\n")

// for {

// sbom, err := iterator.Next(ctx.Context)
// if err == io.EOF {
// break // No more sboms
// }

// if err != nil {
// logger.LogError(ctx.Context, err, "Error retrieving SBOM from iterator")
// }

// // update processor with current SBOM data
// processor.Update(sbom.Data, "", sbom.Path)

// doc, err := processor.ProcessSBOMs()
// if err != nil {
// logger.LogError(ctx.Context, err, "Failed to process SBOM")
// continue
// }

// // if outputDir is provided, save the SBOM file
// if outputDir != "" {
// if err := processor.WriteSBOM(doc, ""); err != nil {
// logger.LogError(ctx.Context, err, "Failed to write SBOM to output directory")
// }
// }

// // Print SBOM content if verbose mode is enabled
// if verbose {
// fmt.Println("\n-------------------- 📜 SBOM Content --------------------")
// fmt.Printf("📂 Filename: %s\n", doc.Filename)
// fmt.Printf("📦 Format: %s | SpecVersion: %s\n\n", doc.Format, doc.SpecVersion)
// fmt.Println(string(doc.Content))
// fmt.Println("------------------------------------------------------")
// fmt.Println()
// }

// sbomCount++
// fmt.Printf(" - 📁 Folder: %s | Format: %s | SpecVersion: %s | Filename: %s \n", sbom.Namespace, doc.Format, doc.SpecVersion, doc.Filename)

// }

// return nil
// }
128 changes: 15 additions & 113 deletions pkg/target/folder/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ package folder

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/google/uuid"
"github.com/interlynk-io/sbommv/pkg/iterator"
"github.com/interlynk-io/sbommv/pkg/logger"
"github.com/interlynk-io/sbommv/pkg/tcontext"
Expand All @@ -30,9 +27,9 @@ import (

// FolderAdapter handles storing SBOMs in a local folder
type FolderAdapter struct {
Role types.AdapterRole
FolderPath string
settings types.UploadSettings
Role types.AdapterRole
config *FolderConfig
Uploader SBOMUploader
}

// AddCommandParams defines folder adapter CLI flags
Expand Down Expand Up @@ -83,10 +80,13 @@ func (f *FolderAdapter) ParseAndValidateParams(cmd *cobra.Command) error {
return fmt.Errorf("invalid input adapter flag usage:\n %s\n\nUse 'sbommv transfer --help' for correct usage.", strings.Join(invalidFlags, "\n "))
}

f.FolderPath = folderPath
f.settings.ProcessingMode = types.UploadMode(mode)
cfg := FolderConfig{
FolderPath: folderPath,
Settings: types.UploadSettings{ProcessingMode: types.UploadMode(mode)},
}
f.config = &cfg

logger.LogDebug(cmd.Context(), "Folder Output Adapter Initialized", "path", f.FolderPath)
logger.LogDebug(cmd.Context(), "Folder Output Adapter Initialized", "path", f.config.FolderPath)
return nil
}

Expand All @@ -96,111 +96,13 @@ func (i *FolderAdapter) FetchSBOMs(ctx *tcontext.TransferMetadata) (iterator.SBO
}

// UploadSBOMs writes SBOMs to the output folder
func (f *FolderAdapter) UploadSBOMs(ctx *tcontext.TransferMetadata, iterator iterator.SBOMIterator) error {
logger.LogDebug(ctx.Context, "Starting SBOM upload", "mode", f.settings.ProcessingMode)

if f.settings.ProcessingMode != "sequential" {
return fmt.Errorf("unsupported processing mode: %s", f.settings.ProcessingMode) // Future-proofed for parallel & batch
}

switch f.settings.ProcessingMode {

case types.UploadParallel:
// TODO: cuncurrent upload: As soon as we get the SBOM, upload it
// f.uploadParallel()
return fmt.Errorf("processing mode %q not yet implemented", f.settings.ProcessingMode)

case types.UploadBatching:
// TODO: hybrid of sequential + parallel
// f.uploadBatch()
return fmt.Errorf("processing mode %q not yet implemented", f.settings.ProcessingMode)

case types.UploadSequential:
// Sequential Processing: Fetch SBOM → Upload → Repeat
f.uploadSequential(ctx, iterator)

default:
//
return fmt.Errorf("invalid processing mode: %q", f.settings.ProcessingMode)
}

logger.LogDebug(ctx.Context, "All SBOMs have been successfully saved in directory", "value", f.FolderPath)
return nil
func (f *FolderAdapter) UploadSBOMs(ctx *tcontext.TransferMetadata, iter iterator.SBOMIterator) error {
logger.LogDebug(ctx.Context, "Starting SBOM upload", "mode", f.config.Settings.ProcessingMode)
return f.Uploader.Upload(ctx, f.config, iter)
}

// DryRun for Output Adapter: Simulates writing SBOMs to a folder
func (f *FolderAdapter) DryRun(ctx *tcontext.TransferMetadata, sbomIter iterator.SBOMIterator) error {
logger.LogDebug(ctx.Context, "Dry-run mode: Displaying SBOMs that would be stored in folder")

fmt.Println("\n📦 **Folder Output Adapter Dry-Run**")

sbomCount := 0

for {
sbom, err := sbomIter.Next(ctx.Context)
if err != nil {
if err.Error() == "EOF" {
break
}
logger.LogError(ctx.Context, err, "Error retrieving SBOM from iterator")
continue
}

namespace := filepath.Base(sbom.Namespace)
if namespace == "" {
namespace = fmt.Sprintf("sbom_%s.json", uuid.New().String()) // Generate unique filename
}

outputPath := filepath.Join(f.FolderPath, namespace)
outputFile := filepath.Join(outputPath, sbom.Path)

fmt.Printf("- 📂 Would write: %s\n", outputFile)
sbomCount++
}

fmt.Printf("\n📊 Total SBOMs to be stored: %d\n", sbomCount)
logger.LogDebug(ctx.Context, "Dry-run mode completed for folder output adapter", "total_sboms", sbomCount)
return nil
}

func (f *FolderAdapter) uploadSequential(ctx *tcontext.TransferMetadata, sbomIter iterator.SBOMIterator) error {
logger.LogDebug(ctx.Context, "Writing SBOMs in sequential mode", "folder", f.FolderPath)

// Process SBOMs
for {
sbom, err := sbomIter.Next(ctx.Context)
if err != nil {
if err.Error() == "EOF" {
break
}
logger.LogError(ctx.Context, err, "Error retrieving SBOM from iterator")
continue
}

namespace := filepath.Base(sbom.Namespace)
if namespace == "" {
namespace = fmt.Sprintf("sbom_%s.json", uuid.New().String()) // Generate unique filename
}

// Construct output path (preserve filename if available)
outputDir := filepath.Join(f.FolderPath, namespace)
if err := os.MkdirAll(outputDir, 0o755); err != nil {
logger.LogError(ctx.Context, err, "Failed to create folder", "path", outputDir)
continue
}

outputFile := filepath.Join(outputDir, sbom.Path)
if sbom.Path == "" {
outputFile = filepath.Join(outputDir, fmt.Sprintf("%s.sbom.json", uuid.New().String()))
}

// Write SBOM file
if err := os.WriteFile(outputFile, sbom.Data, 0o644); err != nil {
logger.LogError(ctx.Context, err, "Failed to write SBOM file", "path", outputFile)
continue
}

logger.LogDebug(ctx.Context, "Successfully written SBOM", "path", outputFile)
}
return nil
func (f *FolderAdapter) DryRun(ctx *tcontext.TransferMetadata, iter iterator.SBOMIterator) error {
reporter := NewFolderOutputReporter(f.config.FolderPath)
return reporter.DryRun(ctx.Context, iter)
}
28 changes: 28 additions & 0 deletions pkg/target/folder/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2025 Interlynk.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package folder

import "github.com/interlynk-io/sbommv/pkg/types"

type FolderConfig struct {
FolderPath string
Settings types.UploadSettings
}

func NewFolderConfig() *FolderConfig {
return &FolderConfig{
Settings: types.UploadSettings{ProcessingMode: types.UploadSequential},
}
}
68 changes: 68 additions & 0 deletions pkg/target/folder/reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2025 Interlynk.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package folder

import (
"context"
"fmt"
"io"
"path/filepath"

"github.com/google/uuid"
"github.com/interlynk-io/sbommv/pkg/iterator"
"github.com/interlynk-io/sbommv/pkg/logger"
)

type FolderOutputReporter struct {
folderPath string
}

func NewFolderOutputReporter(folderPath string) *FolderOutputReporter {
return &FolderOutputReporter{folderPath: folderPath}
}

func (r *FolderOutputReporter) DryRun(ctx context.Context, iter iterator.SBOMIterator) error {
logger.LogDebug(ctx, "Dry-run mode: Displaying SBOMs for folder output")
fmt.Println("\n📦 **Folder Output Adapter Dry-Run**")
sbomCount := 0

for {
sbom, err := iter.Next(ctx)
if err == io.EOF {
break
}
if err != nil {
logger.LogError(ctx, err, "Error retrieving SBOM from iterator")
return err
}

namespace := filepath.Base(sbom.Namespace)
if namespace == "" {
namespace = fmt.Sprintf("sbom_%s.json", uuid.New().String())
}
outputPath := filepath.Join(r.folderPath, namespace)
outputFile := filepath.Join(outputPath, sbom.Path)
if sbom.Path == "" {
outputFile = filepath.Join(outputPath, fmt.Sprintf("%s.sbom.json", uuid.New().String()))
}

fmt.Printf("- 📂 Would write: %s\n", outputFile)
sbomCount++
}

fmt.Printf("\n📊 Total SBOMs to be stored: %d\n", sbomCount)
logger.LogDebug(ctx, "Dry-run completed", "total_sboms", sbomCount)
return nil
}
Loading

0 comments on commit e3617ca

Please sign in to comment.