Skip to content
Open
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
14 changes: 11 additions & 3 deletions files/tests/sanitization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,17 @@ func TestSanitizeFile(t *testing.T) {
".zip": true,
}

tempDir, _ := os.MkdirTemp(" ", "temp-tests")
defer os.RemoveAll(tempDir)
// tempDir := "./temp"
// Create a temporary directory for the tests and handle potential errors.
tempDir, err := os.MkdirTemp(" ", "temp-tests")
if err != nil {
t.Fatalf("Failed to create temporary directory: %v", err)
}
// Defer cleanup and log any error that occurs during cleanup.
defer func() {
if err := os.RemoveAll(tempDir); err != nil {
t.Logf("Warning: failed to remove temporary directory %s: %v", tempDir, err)
}
}()

for _, tc := range testCases {

Expand Down
19 changes: 16 additions & 3 deletions generators/github/git_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,28 @@ import (
"strings"

"github.com/meshery/meshkit/generators/models"
"github.com/meshery/meshkit/logger"
"github.com/meshery/meshkit/utils"
"github.com/meshery/meshkit/utils/helm"
"github.com/meshery/meshkit/utils/walker"
)

type GitRepo struct {
// <git://github.com/owner/repo/branch/versiontag/root(path to the directory/file)>
URL *url.URL
URL *url.URL
PackageName string
}

// Assumpations: 1. Always a K8s manifest
// 2. Unzipped/unarchived File type

func (gr GitRepo) GetContent() (models.Package, error) {
log, err := logger.New("generators-github", logger.Options{})
if err != nil {
// If logger fails to initialize, we can't proceed without logging capabilities.
// Alternatively, one could fall back to fmt.Println and continue.
return nil, err
}
gitWalker := walker.NewGit()

owner, repo, branch, root, err := gr.extractRepoDetailsFromSourceURL()
Expand All @@ -47,9 +54,15 @@ func (gr GitRepo) GetContent() (models.Package, error) {
br := bufio.NewWriter(fd)

defer func() {
br.Flush()
fd.Close()
if err := br.Flush(); err != nil {
log.Warnf("failed to flush writer: %v", err)
}

if err := fd.Close(); err != nil {
log.Warnf("failed to close file: %v", err)
}
}()

gw := gitWalker.
Owner(owner).
Repo(repo).
Expand Down
2 changes: 1 addition & 1 deletion utils/cue.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func Lookup(rootVal cue.Value, path string) (cue.Value, error) {
return res, ErrCueLookup(res.Err())
}
if !res.Exists() {
return res, ErrCueLookup(fmt.Errorf("Could not find the value at the path: %s", path))
return res, ErrCueLookup(fmt.Errorf("could not find the value at the path: %s", path))
}

return res.Value(), nil
Expand Down
46 changes: 29 additions & 17 deletions utils/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@ var (
ErrCreateDirCode = "meshkit-11182"
// ErrDecodeYamlCode represents the error which is generated when yaml
// decode process fails
ErrDecodeYamlCode = "meshkit-11183"
ErrExtractTarXZCode = "meshkit-11184"
ErrExtractZipCode = "meshkit-11185"
ErrReadDirCode = "meshkit-11186"
ErrInvalidSchemaVersionCode = "meshkit-11273"
ErrFileWalkDirCode = "meshkit-11274"
ErrRelPathCode = "meshkit-11275"
ErrCopyFileCode = "meshkit-11276"
ErrCloseFileCode = "meshkit-11277"
ErrCompressToTarGZCode = "meshkit-11248"
ErrDecodeYamlCode = "meshkit-11183"
ErrExtractTarXZCode = "meshkit-11184"
ErrExtractZipCode = "meshkit-11185"
ErrReadDirCode = "meshkit-11186"
ErrInvalidSchemaVersionCode = "meshkit-11273"
ErrFileWalkDirCode = "meshkit-11274"
ErrRelPathCode = "meshkit-11275"
ErrCopyFileCode = "meshkit-11276"
ErrCloseFileCode = "meshkit-11277"
ErrCompressToTarGZCode = "meshkit-11248"
ErrUnsupportedTarHeaderTypeCode = "meshkit-11282"

ErrConvertToByteCode = "meshkit-11187"

Expand All @@ -64,9 +65,9 @@ func ErrInvalidConstructSchemaVersion(contruct string, version string, supported
ErrInvalidSchemaVersionCode,
errors.Critical,
[]string{"Invalid schema version " + version},
[]string{"The `schemaVersion` key is either empty or has an incorrect value."},
[]string{"The schemaVersion key is either empty or has an incorrect value."},
[]string{fmt.Sprintf("The schema is not of type '%s'", contruct)},
[]string{"Verify that `schemaVersion` key should be '%s'", supportedVersion},
[]string{"Verify that schemaVersion key should be '%s'", supportedVersion},
)
}

Expand All @@ -75,17 +76,17 @@ var (
ErrUnmarshalTypeCode,
errors.Alert,
[]string{"Invalid extraction type"},
[]string{"The file type to be extracted is neither `tar.gz` nor `zip`."},
[]string{"Invalid object format. The file is not of type `zip` or `tar.gz`."},
[]string{"Make sure to check that the file type is `zip` or `tar.gz`."},
[]string{"The file type to be extracted is neither tar.gz nor zip."},
[]string{"Invalid object format. The file is not of type zip or tar.gz."},
[]string{"Make sure to check that the file type is zip or tar.gz."},
)
ErrInvalidSchemaVersion = errors.New(
ErrInvalidSchemaVersionCode,
errors.Alert,
[]string{"Invalid schema version"},
[]string{"The `schemaVersion` key is either empty or has an incorrect value."},
[]string{"The schemaVersion key is either empty or has an incorrect value."},
[]string{"The schema is not of type 'relationship', 'component', 'model' , 'policy'."},
[]string{"Verify that `schemaVersion` key should be either `relationships.meshery.io`, `component.meshery.io`, `model.meshery.io` or `policy.meshery.io`."},
[]string{"Verify that schemaVersion key should be either relationships.meshery.io, component.meshery.io, model.meshery.io or policy.meshery.io."},
)
)

Expand Down Expand Up @@ -272,3 +273,14 @@ func ErrGoogleSheetSRV(err error) error {
func ErrWritingIntoFile(err error, obj string) error {
return errors.New(ErrWritingIntoFileCode, errors.Alert, []string{fmt.Sprintf("failed to write into file %s", obj)}, []string{err.Error()}, []string{"Insufficient permissions to write into file", "file might be corrupted"}, []string{"check if sufficient permissions are givent to the file", "check if the file is corrupted"})
}

func ErrUnsupportedTarHeaderType(typeflag byte, name string) error {
return errors.New(
ErrUnsupportedTarHeaderTypeCode,
errors.Alert,
[]string{"Unsupported tar header type encountered during extraction"},
[]string{fmt.Sprintf("The tar archive contains an entry '%s' with an unsupported type flag '%c'.", name, typeflag)},
[]string{"The tar archive is malformed or contains an entry type that this utility cannot handle (e.g., special device files)."},
[]string{"Ensure the tar archive was created with standard file types (directories, regular files, symlinks).", "Check the integrity of the archive file."},
)
}
108 changes: 68 additions & 40 deletions utils/kubernetes/kompose/convert.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package kompose

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

"github.com/kubernetes/kompose/client"
"github.com/meshery/meshkit/logger"
"github.com/meshery/meshkit/utils"
"gopkg.in/yaml.v3"
)

const DefaultDockerComposeSchemaURL = "https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json"

// Checks whether the given manifest is a valid docker-compose file.
// schemaURL is assigned a default url if not specified
// error will be 'nil' if it is a valid docker compose file
// IsManifestADockerCompose checks whether the given manifest is a valid docker-compose file.
// schemaURL is assigned a default url if not specified.
// The error will be 'nil' if it is a valid docker compose file.
func IsManifestADockerCompose(manifest []byte, schemaURL string) error {
if schemaURL == "" {
schemaURL = DefaultDockerComposeSchemaURL
Expand All @@ -28,51 +30,72 @@ func IsManifestADockerCompose(manifest []byte, schemaURL string) error {
return err
}

// converts a given docker-compose file into kubernetes manifests
// expects a validated docker-compose file
// Convert converts a given docker-compose file into kubernetes manifests.
// It expects a validated docker-compose file.
func Convert(dockerCompose DockerComposeFile) (string, error) {
// Get user's home directory
// Initialize a logger for this function's scope.
log, err := logger.New("kompose-convert", logger.Options{})
if err != nil {
// If the logger fails, print an error and continue without logging.
fmt.Printf("Logger initialization failed: %v\n", err)
}

// Get user's home directory.
homeDir, err := os.UserHomeDir()
if err != nil {
return "", ErrCvrtKompose(err)
}

// Construct path to .meshery directory
// Construct path to .meshery directory.
mesheryDir := filepath.Join(homeDir, ".meshery")
tempFilePath := filepath.Join(mesheryDir, "temp.data")
resultFilePath := filepath.Join(mesheryDir, "result.yaml")

if err := utils.CreateFile(dockerCompose, "temp.data", mesheryDir); err != nil {
// Defer cleanup to ensure temp files are removed, even on error.
defer func() {
// Log cleanup errors as warnings, as the primary function error is more important.
if err := os.Remove(tempFilePath); err != nil && !os.IsNotExist(err) {
log.Warnf("failed to remove temporary file %s: %v", tempFilePath, err)
}
if err := os.Remove(resultFilePath); err != nil && !os.IsNotExist(err) {
log.Warnf("failed to remove result file %s: %v", resultFilePath, err)
}
}()

// Format the Docker Compose file content to be compatible with Kompose.
if err := formatComposeFile(&dockerCompose); err != nil {
return "", ErrCvrtKompose(err)
}

defer func() {
os.Remove(tempFilePath)
os.Remove(resultFilePath)
}()
// Create a temporary file with the formatted compose data.
if err := utils.CreateFile(dockerCompose, "temp.data", mesheryDir); err != nil {
return "", ErrCvrtKompose(err)
}

formatComposeFile(&dockerCompose)
err = versionCheck(dockerCompose)
if err != nil {
// Check for version compatibility.
if err := versionCheck(dockerCompose); err != nil {
return "", ErrCvrtKompose(err)
}

// Initialize the Kompose client.
komposeClient, err := client.NewClient()
if err != nil {
return "", ErrCvrtKompose(err)
}

// Set up the conversion options.
ConvertOpt := client.ConvertOptions{
InputFiles: []string{tempFilePath},
OutFile: resultFilePath,
GenerateNetworkPolicies: true,
}

_, err = komposeClient.Convert(ConvertOpt)
if err != nil {
// Perform the conversion using the Kompose client.
if _, err = komposeClient.Convert(ConvertOpt); err != nil {
return "", ErrCvrtKompose(err)
}

// Read the resulting Kubernetes manifest file.
result, err := os.ReadFile(resultFilePath)
if err != nil {
return "", ErrCvrtKompose(err)
Expand All @@ -81,13 +104,27 @@ func Convert(dockerCompose DockerComposeFile) (string, error) {
return string(result), nil
}

type composeFile struct {
Version string `yaml:"version,omitempty"`
// formatComposeFile takes a pointer to the compose file byte array and formats it
// to be compatible with Kompose by unmarshalling and marshalling it.
// It expects a validated Docker Compose file.
func formatComposeFile(yamlManifest *DockerComposeFile) error {
data := composeFile{}
if err := yaml.Unmarshal(*yamlManifest, &data); err != nil {
return fmt.Errorf("failed to unmarshal compose file: %w", err)
}

// Marshal it again to ensure it's in the correct format for Kompose.
out, err := yaml.Marshal(data)
if err != nil {
return fmt.Errorf("failed to marshal compose file: %w", err)
}

*yamlManifest = out
return nil
}

// checks if the version is compatible with `kompose`
// expects a valid docker compose yaml
// error = nil means it is compatible
// versionCheck checks if the version in the Docker Compose file is compatible with Kompose.
// It expects a valid Docker Compose YAML and returns an error if the version is not supported.
func versionCheck(dc DockerComposeFile) error {
cf := composeFile{}
err := yaml.Unmarshal(dc, &cf)
Expand All @@ -100,26 +137,17 @@ func versionCheck(dc DockerComposeFile) error {
versionFloatVal, err := strconv.ParseFloat(cf.Version, 64)
if err != nil {
return utils.ErrExpectedTypeMismatch(err, "float")
} else {
if versionFloatVal > 3.9 {
return ErrIncompatibleVersion()
}
}

if versionFloatVal > 3.9 {
return ErrIncompatibleVersion()
}

return nil
}

// formatComposeFile takes in a pointer to the compose file byte array and formats it so that it is compatible with `Kompose`
// it expects a validated docker compose file and does not validate
func formatComposeFile(yamlManifest *DockerComposeFile) {
data := composeFile{}
err := yaml.Unmarshal(*yamlManifest, &data)
if err != nil {
return
}
// so that "3.3" and 3.3 are treated differently by `Kompose`
out, err := yaml.Marshal(data)
if err != nil {
return
}
*yamlManifest = out
// composeFile represents the structure needed to extract the version
// from a Docker Compose file.
type composeFile struct {
Version string `yaml:"version,omitempty"`
}
Loading