From 3f6f61a41113f60b12c4cae3d6b051e44a383a51 Mon Sep 17 00:00:00 2001 From: Nithish Date: Sat, 18 Jun 2022 15:05:10 +0530 Subject: [PATCH] refactor: Add validation, formatting and errors for kompose conversion code Signed-off-by: Nithish --- helpers/component_info.json | 2 +- utils/error.go | 12 ++++- utils/kubernetes/kompose/convert.go | 5 +- utils/kubernetes/kompose/error.go | 12 ++++- .../kubernetes/kompose/models/composefile.go | 4 +- utils/kubernetes/kompose/utils.go | 48 ++++++++++++++++++- 6 files changed, 74 insertions(+), 9 deletions(-) diff --git a/helpers/component_info.json b/helpers/component_info.json index fccdea67..f4653123 100644 --- a/helpers/component_info.json +++ b/helpers/component_info.json @@ -1,5 +1,5 @@ { "name": "meshkit", "type": "library", - "next_error_code": 11076 + "next_error_code": 11080 } diff --git a/utils/error.go b/utils/error.go index 0b7310ca..56203b0d 100644 --- a/utils/error.go +++ b/utils/error.go @@ -22,10 +22,20 @@ var ( ErrReadingLocalFileCode = "11054" ErrGettingLatestReleaseTagCode = "11055" ErrInvalidProtocol = errors.New(ErrInvalidProtocolCode, errors.Alert, []string{"invalid protocol: only http, https and file are valid protocols"}, []string{}, []string{"Network protocol is incorrect"}, []string{"Make sure to specify the right network protocol"}) + ErrMissingFieldCode = "11076" + ErrExpectedTypeMismatchCode = "11079" ) +func ErrExpectedTypeMismatch(err error, expectedType string) error { + return errors.New(ErrExpectedTypeMismatchCode, errors.Alert, []string{"Expected the type to be: ", expectedType}, []string{err.Error()}, []string{"Invalid manifest"}, []string{"Make sure that the value provided in the manifest has the needed type."}) +} + +func ErrMissingField(err error, missingFieldName string) error { + return errors.New(ErrMissingFieldCode, errors.Alert, []string{"Missing field or property with name: ", missingFieldName}, []string{err.Error()}, []string{"Invalid manifest"}, []string{"Make sure that the concerned data type has all the required fields/values."}) +} + func ErrUnmarshal(err error) error { - return errors.New(ErrUnmarshalCode, errors.Alert, []string{"Unmarshal unknown error: %s"}, []string{err.Error()}, []string{"Invalid object format"}, []string{"Make sure to input a valid JSON object"}) + return errors.New(ErrUnmarshalCode, errors.Alert, []string{"Unmarshal unknown error: "}, []string{err.Error()}, []string{"Invalid object format"}, []string{"Make sure to input a valid JSON object"}) } func ErrUnmarshalInvalid(err error, typ reflect.Type) error { diff --git a/utils/kubernetes/kompose/convert.go b/utils/kubernetes/kompose/convert.go index 719633f6..e5e2fb80 100644 --- a/utils/kubernetes/kompose/convert.go +++ b/utils/kubernetes/kompose/convert.go @@ -7,6 +7,7 @@ import ( "github.com/kubernetes/kompose/pkg/app" "github.com/kubernetes/kompose/pkg/kobject" + "github.com/layer5io/meshkit/utils" "gopkg.in/yaml.v2" ) @@ -15,8 +16,8 @@ var ( ) // converts a given docker-compose file into kubernetes manifests -func Convert(dockerCompose string) (string, error) { - err := ioutil.WriteFile("temp.data", []byte(dockerCompose), 0666) +func Convert(dockerCompose []byte) (string, error) { + err := utils.CreateFile(dockerCompose, "temp.data", "./") if err != nil { return "", ErrCvrtKompose(err) } diff --git a/utils/kubernetes/kompose/error.go b/utils/kubernetes/kompose/error.go index 2b4d0b31..9cb36eff 100644 --- a/utils/kubernetes/kompose/error.go +++ b/utils/kubernetes/kompose/error.go @@ -3,9 +3,19 @@ package kompose import "github.com/layer5io/meshkit/errors" const ( - ErrCvrtKomposeCode = "11075" + ErrCvrtKomposeCode = "11075" + ErrNoVersionCode = "11077" + ErrIncompatibleVersionCode = "11078" ) func ErrCvrtKompose(err error) error { return errors.New(ErrCvrtKomposeCode, errors.Alert, []string{"Error converting the docker compose file into kubernetes manifests"}, []string{err.Error()}, []string{"Could not convert docker-compose file into kubernetes manifests"}, []string{"Make sure the docker-compose file is valid", ""}) } + +func ErrNoVersion() error { + return errors.New(ErrNoVersionCode, errors.Alert, []string{"version not found in the docker compose file"}, []string{"The docker compose file does not have version field in it. The underlying tool that is used for conversion mandates the presence of version field."}, []string{"Since the Docker Compose specification does not mandate the version field from version 3 onwards, most sources do not provide them."}, []string{"Make sure that the compose file has version specified,", "Add any version less than or equal to 3.3 if you cannot get the exact version from the source"}) +} + +func ErrIncompatibleVersion() error { + return errors.New(ErrIncompatibleVersionCode, errors.Alert, []string{"This version of docker compose file is not compatible."}, []string{"This docker compose file is invalid since it's version is incompatible."}, []string{"docker compose file with version greater than 3.3 is probably being used"}, []string{"Make sure that the compose file has version less than or equal to 3.3,", ""}) +} diff --git a/utils/kubernetes/kompose/models/composefile.go b/utils/kubernetes/kompose/models/composefile.go index e5e37038..52e384ef 100644 --- a/utils/kubernetes/kompose/models/composefile.go +++ b/utils/kubernetes/kompose/models/composefile.go @@ -1,8 +1,6 @@ package models -import "encoding/json" - type DockerComposeFile struct { - Version json.Number `yaml:"version" json:"version"` + Version string `yaml:"version" json:"version"` Services interface{} `yaml:"services" json:"services"` // more constraints should be added to this type } diff --git a/utils/kubernetes/kompose/utils.go b/utils/kubernetes/kompose/utils.go index 86f20d8b..9e51ada7 100644 --- a/utils/kubernetes/kompose/utils.go +++ b/utils/kubernetes/kompose/utils.go @@ -1,6 +1,10 @@ package kompose import ( + "fmt" + "strconv" + + errors "github.com/layer5io/meshkit/utils" "github.com/layer5io/meshkit/utils/kubernetes/kompose/models" "gopkg.in/yaml.v2" ) @@ -11,8 +15,50 @@ func IsManifestADockerCompose(yamlManifest []byte) bool { if err := yaml.Unmarshal(yamlManifest, &data); err != nil { return false } - if data.Version == "" { + if data.Services == nil { return false } return true } + +// VaildateDockerComposeFile takes in a manifest and returns validates it +func VaildateDockerComposeFile(yamlManifest []byte) error { + data := models.DockerComposeFile{} + if err := yaml.Unmarshal(yamlManifest, &data); err != nil { + return errors.ErrUnmarshal(err) + } + if data.Version == "" { + return errors.ErrMissingField(ErrNoVersion(), "Version") + } + versionFloatVal, err := strconv.ParseFloat(data.Version, 64) + if err != nil { + return errors.ErrExpectedTypeMismatch(err, "float") + } else { + if versionFloatVal > 3.3 { + // kompose throws a fatal error when version exceeds 3.3 + // need this till this PR gets merged https://github.com/kubernetes/kompose/pull/1440(move away from libcompose to compose-go) + return ErrIncompatibleVersion() + } + } + if data.Services == nil { + return errors.ErrMissingField(fmt.Errorf("Services field is missing in the docker compose file"), "Services") + } + 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 *[]byte) error { + data := models.DockerComposeFile{} + err := yaml.Unmarshal(*yamlManifest, &data) + if err != nil { + return errors.ErrUnmarshal(err) + } + data.Version = fmt.Sprintf("%s", data.Version) + out, err := yaml.Marshal(data) + if err != nil { + return errors.ErrMarshal(err) + } + *yamlManifest = out + return nil +}