Skip to content

Commit

Permalink
Merge pull request #54 from interlynk-io/feature/folder_system
Browse files Browse the repository at this point in the history
added folder system as a input and output adapter
  • Loading branch information
riteshnoronha authored Feb 21, 2025
2 parents 6e29a03 + e3617ca commit 1a9a2d0
Show file tree
Hide file tree
Showing 16 changed files with 851 additions and 11 deletions.
15 changes: 13 additions & 2 deletions cmd/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import (
"fmt"

"github.com/interlynk-io/sbommv/pkg/engine"
ifolder "github.com/interlynk-io/sbommv/pkg/source/folder"
ofolder "github.com/interlynk-io/sbommv/pkg/target/folder"

"github.com/interlynk-io/sbommv/pkg/source/github"
"github.com/interlynk-io/sbommv/pkg/target/interlynk"
"github.com/interlynk-io/sbommv/pkg/types"
Expand Down Expand Up @@ -69,10 +72,18 @@ func registerAdapterFlags(cmd *cobra.Command) {
githubAdapter := &github.GitHubAdapter{}
githubAdapter.AddCommandParams(cmd)

// Register Input Folder Adapter Flags
folderInputAdapter := &ifolder.FolderAdapter{}
folderInputAdapter.AddCommandParams(cmd)

// Register Interlynk Adapter Flags
interlynkAdapter := &interlynk.InterlynkAdapter{}
interlynkAdapter.AddCommandParams(cmd)

// Register Output Folder Adapter Flags
folderOutputAdapter := &ofolder.FolderAdapter{}
folderOutputAdapter.AddCommandParams(cmd)

// similarly for all other Adapters
}

Expand Down Expand Up @@ -109,8 +120,8 @@ func parseConfig(cmd *cobra.Command) (types.Config, error) {
outputType, _ := cmd.Flags().GetString("output-adapter")
dr, _ := cmd.Flags().GetBool("dry-run")

validInputAdapter := map[string]bool{"github": true}
validOutputAdapter := map[string]bool{"interlynk": true}
validInputAdapter := map[string]bool{"github": true, "folder": true}
validOutputAdapter := map[string]bool{"interlynk": true, "folder": true}

// Custom validation for required flags
missingFlags := []string{}
Expand Down
106 changes: 106 additions & 0 deletions docs/how_to_plugin_new_adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# 📖 Writing a New Adapter for sbommv

## Understanding Adapters in sbommv

`sbommv` follows a pluggable architecture where adapters act as interfaces between SBOM sources and destinations.

- **Input Adapters** → Fetch SBOMs (e.g., from GitHub, Folder, etc.).
- **Output Adapters** → Send SBOMs (e.g., to Interlynk, Folder, etc.).
- **Each Adapter Implements the** `Adapter` **Interface** → This ensures a common API across different sources & destinations.

## Implementing a New Adapter

To add a new adapter, follow these steps:

### Step 1: Define a Struct for Your Adapter

Each adapter has its own struct. This struct will hold relevant configuration details.

For **FolderAdapter**, we define:

```go
// FolderAdapter struct represents an adapter for local folder storage
type FolderAdapter struct {
Role types.AdapterRole
FolderPath string
Recursive bool
}
```

💡 **Note**:

- **Role**: Defines whether the adapter is for input or output.
- **FolderPath**: Directory to scan or store SBOMs.
- **Recursive**: If true, scans subdirectories when acting as an input adapter.

### Step 2: Implement `AddCommandParams`

- This method adds CLI flags related to the adapter.

```go
// AddCommandParams adds folder adapter-specific CLI flags
func (f *FolderAdapter) AddCommandParams(cmd *cobra.Command) {
// implementation code
}
```

- This ensures the correct flags are registered depending on the adapter role.

### Step 3: Implement `ParseAndValidateParams`

- This method validates and extracts CLI parameters.

```go
// ParseAndValidateParams extracts and validates folder adapter parameters
func (f *FolderAdapter) ParseAndValidateParams(cmd *cobra.Command) error {
// implementation code
}
```

- This ensures we have valid folder paths before proceeding.

### Step 4: Implement `FetchSBOMs` for Input Adapter

- This method scans a folder, detects SBOMs, and returns an iterator.

```go
// FetchSBOMs retrieves SBOMs from the specified folder
func (f *FolderAdapter) FetchSBOMs(ctx *tcontext.TransferMetadata) (iterator.SBOMIterator, error) {
// implementation code
}
```

- This method:

- Scans the directory recursively, if resursive flag is `true`.
- Detects SBOMs using utils.IsValidSBOM().
- Returns an iterator for processing SBOMs.

### Step 5: Implement UploadSBOMs for Output Adapter

- This method saves SBOMs to the specified folder.

```go
// UploadSBOMs writes SBOMs to the specified folder
func (f *FolderAdapter) UploadSBOMs(ctx *tcontext.TransferMetadata, it iterator.SBOMIterator) error {
// implementation code
}
```

- This method:
- Creates a folder if it doesn’t exist.
- Writes SBOMs using either their original filename or a generated UUID.

### Step 6: Implement DryRun

```go
// DryRun simulates fetching or uploading SBOMs
func (f *FolderAdapter) DryRun(ctx *tcontext.TransferMetadata, it iterator.SBOMIterator) error {
// implementation code
}
```

- This method:

- In input mode, lists detected SBOMs.
- In output mode, shows where SBOMs will be saved.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require github.com/google/go-cmp v0.6.0 // indirect

require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/google/uuid v1.6.0
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down
10 changes: 8 additions & 2 deletions pkg/adapter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (

"github.com/interlynk-io/sbommv/pkg/iterator"
"github.com/interlynk-io/sbommv/pkg/logger"
ofolder "github.com/interlynk-io/sbommv/pkg/target/folder"

ifolder "github.com/interlynk-io/sbommv/pkg/source/folder"
"github.com/interlynk-io/sbommv/pkg/source/github"
"github.com/interlynk-io/sbommv/pkg/target/interlynk"
"github.com/interlynk-io/sbommv/pkg/tcontext"
Expand Down Expand Up @@ -58,6 +61,9 @@ func NewAdapter(ctx *tcontext.TransferMetadata, config types.Config) (map[types.
case types.GithubAdapterType:
adapters[types.InputAdapterRole] = &github.GitHubAdapter{Role: types.InputAdapterRole}

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

case types.InterlynkAdapterType:
adapters[types.InputAdapterRole] = &interlynk.InterlynkAdapter{Role: types.InputAdapterRole}

Expand All @@ -72,8 +78,8 @@ func NewAdapter(ctx *tcontext.TransferMetadata, config types.Config) (map[types.

switch types.AdapterType(config.DestinationType) {

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

case types.InterlynkAdapterType:
adapters[types.OutputAdapterRole] = &interlynk.InterlynkAdapter{Role: types.OutputAdapterRole}
Expand Down
117 changes: 117 additions & 0 deletions pkg/source/folder/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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 (
"fmt"
"strings"

"github.com/interlynk-io/sbommv/pkg/iterator"
"github.com/interlynk-io/sbommv/pkg/logger"
"github.com/interlynk-io/sbommv/pkg/tcontext"
"github.com/interlynk-io/sbommv/pkg/types"
"github.com/spf13/cobra"
)

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

// AddCommandParams adds Folder-specific CLI flags
func (f *FolderAdapter) AddCommandParams(cmd *cobra.Command) {
cmd.Flags().String("in-folder-path", "", "Folder path")
cmd.Flags().Bool("in-folder-recursive", false, "Folder recurssive (default: false)")
cmd.Flags().String("in-folder-processing-mode", "sequential", "Folder processing mode (sequential/parallel)")
}

// ParseAndValidateParams validates the Folder adapter params
func (f *FolderAdapter) ParseAndValidateParams(cmd *cobra.Command) error {
var (
pathFlag, recursiveFlag, processingModeFlag string
missingFlags []string
invalidFlags []string
)

switch f.Role {
case types.InputAdapterRole:
pathFlag = "in-folder-path"
recursiveFlag = "in-folder-recursive"
processingModeFlag = "in-folder-processing-mode"

case types.OutputAdapterRole:
return fmt.Errorf("The Folder adapter doesn't support output adapter functionalities.")

default:
return fmt.Errorf("The adapter is neither an input type nor an output type")

}

// Extract Folder Path
folderPath, _ := cmd.Flags().GetString(pathFlag)
if folderPath == "" {
missingFlags = append(missingFlags, "--"+pathFlag)
}

// Extract Folder Path
folderRecurse, _ := cmd.Flags().GetBool(recursiveFlag)

validModes := map[string]bool{"sequential": true, "parallel": true}

// Extract the processing mode: sequential/parallel
mode, _ := cmd.Flags().GetString(processingModeFlag)
if !validModes[mode] {
invalidFlags = append(invalidFlags, fmt.Sprintf("%s=%s (must be one of: sequential, parallel mode)", processingModeFlag, mode))
}

// Validate required flags
if len(missingFlags) > 0 {
return fmt.Errorf("missing input adapter required flags: %v\n\nUse 'sbommv transfer --help' for usage details.", missingFlags)
}

// Validate incorrect flag usage
if len(invalidFlags) > 0 {
return fmt.Errorf("invalid input adapter flag usage:\n %s\n\nUse 'sbommv transfer --help' for correct usage.", strings.Join(invalidFlags, "\n "))
}

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)
}

// OutputSBOMs should return an error since Folder does not support SBOM uploads
func (f *FolderAdapter) UploadSBOMs(ctx *tcontext.TransferMetadata, iterator iterator.SBOMIterator) error {
return fmt.Errorf("Folder adapter does not support SBOM uploading")
}

// DryRun for Folder Adapter: Displays all fetched SBOMs from folder adapter
func (f *FolderAdapter) DryRun(ctx *tcontext.TransferMetadata, iter iterator.SBOMIterator) error {
reporter := NewFolderReporter(false, "")
return reporter.DryRun(ctx.Context, iter)
}
30 changes: 30 additions & 0 deletions pkg/source/folder/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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
Recursive bool
ProcessingMode types.ProcessingMode
}

func NewFolderConfig() *FolderConfig {
return &FolderConfig{
ProcessingMode: types.FetchSequential, // Default
}
}
Loading

0 comments on commit 1a9a2d0

Please sign in to comment.