github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/cloudfoundry/ManifestUtils.go (about)

     1  package cloudfoundry
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  
     8  	"github.com/ghodss/yaml"
     9  	"github.com/pkg/errors"
    10  
    11  	"github.com/SAP/jenkins-library/pkg/log"
    12  )
    13  
    14  const constPropApplications = "applications"
    15  const constPropBuildpacks = "buildpacks"
    16  const constPropBuildpack = "buildpack"
    17  
    18  // Manifest ...
    19  type Manifest interface {
    20  	GetFileName() string
    21  	GetAppName(index int) (string, error)
    22  	ApplicationHasProperty(index int, name string) (bool, error)
    23  	GetApplicationProperty(index int, name string) (interface{}, error)
    24  	Transform() error
    25  	IsModified() bool
    26  	GetApplications() ([]map[string]interface{}, error)
    27  	WriteManifest() error
    28  }
    29  
    30  // manifest ...
    31  type manifest struct {
    32  	self     map[string]interface{}
    33  	modified bool
    34  	name     string
    35  }
    36  
    37  var _readFile = os.ReadFile
    38  var _writeFile = os.WriteFile
    39  
    40  // ReadManifest Reads the manifest denoted by 'name'
    41  func ReadManifest(name string) (Manifest, error) {
    42  
    43  	log.Entry().Infof("Reading manifest file  '%s'", name)
    44  
    45  	m := &manifest{self: make(map[string]interface{}), name: name, modified: false}
    46  
    47  	content, err := _readFile(name)
    48  	if err != nil {
    49  		return m, errors.Wrapf(err, "cannot read file '%v'", m.name)
    50  	}
    51  
    52  	err = yaml.Unmarshal(content, &m.self)
    53  	if err != nil {
    54  		return m, errors.Wrapf(err, "Cannot parse yaml file '%s': %s", m.name, string(content))
    55  	}
    56  
    57  	log.Entry().Infof("Manifest file '%s' has been parsed", m.name)
    58  
    59  	return m, nil
    60  }
    61  
    62  // WriteManifest Writes the manifest to the file denoted
    63  // by the name property (GetFileName()). The modified flag is
    64  // resetted after the write operation.
    65  func (m *manifest) WriteManifest() error {
    66  
    67  	d, err := yaml.Marshal(&m.self)
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	log.Entry().Debugf("Writing manifest file '%s'", m.GetFileName())
    73  	err = _writeFile(m.GetFileName(), d, 0644)
    74  
    75  	if err == nil {
    76  		m.modified = false
    77  	}
    78  
    79  	log.Entry().Debugf("Manifest file '%s' has been written", m.name)
    80  	return err
    81  }
    82  
    83  // GetFileName returns the file name of the manifest.
    84  func (m *manifest) GetFileName() string {
    85  	return m.name
    86  }
    87  
    88  // GetApplications Returns all applications denoted in the manifest file.
    89  // The applications are returned as a slice of maps. Each app is represented by
    90  // a map.
    91  func (m *manifest) GetApplications() ([]map[string]interface{}, error) {
    92  	apps, err := toSlice(m.self["applications"])
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	result := make([]map[string]interface{}, 0)
    98  
    99  	for _, app := range apps {
   100  		if _app, ok := app.(map[string]interface{}); ok {
   101  			result = append(result, _app)
   102  		} else {
   103  			return nil, fmt.Errorf("Cannot cast applications to map. Manifest file '%s' has invalid format", m.GetFileName())
   104  		}
   105  	}
   106  	return result, nil
   107  }
   108  
   109  // ApplicationHasProperty Checks if the application denoted by 'index' has the property 'name'
   110  func (m *manifest) ApplicationHasProperty(index int, name string) (bool, error) {
   111  
   112  	sliced, err := toSlice(m.self[constPropApplications])
   113  	if err != nil {
   114  		return false, err
   115  	}
   116  
   117  	if index >= len(sliced) {
   118  		return false, fmt.Errorf("Index (%d) out of bound. Number of apps: %d", index, len(sliced))
   119  	}
   120  
   121  	_m, err := toMap(sliced[index])
   122  	if err != nil {
   123  		return false, err
   124  	}
   125  
   126  	_, ok := _m[name]
   127  
   128  	return ok, nil
   129  }
   130  
   131  // GetApplicationProperty ...
   132  func (m *manifest) GetApplicationProperty(index int, name string) (interface{}, error) {
   133  
   134  	sliced, err := toSlice(m.self[constPropApplications])
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	if index >= len(sliced) {
   140  		return nil, fmt.Errorf("Index (%d) out of bound. Number of apps: %d", index, len(sliced))
   141  	}
   142  
   143  	app, err := toMap(sliced[index])
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	value, exists := app[name]
   149  	if exists {
   150  		return value, nil
   151  	}
   152  
   153  	return nil, fmt.Errorf("No such property: '%s' available in application at position %d", name, index)
   154  }
   155  
   156  // GetAppName Gets the name of the app at 'index'
   157  func (m *manifest) GetAppName(index int) (string, error) {
   158  
   159  	appName, err := m.GetApplicationProperty(index, "name")
   160  	if err != nil {
   161  		return "", err
   162  	}
   163  
   164  	if name, ok := appName.(string); ok {
   165  		return name, nil
   166  	}
   167  
   168  	return "", fmt.Errorf("Cannot retrieve application name for app at index %d", index)
   169  }
   170  
   171  // Transform For each app in the manifest the first entry in the build packs list
   172  // gets moved to the top level under the key 'buildpack'. The 'buildpacks' list is
   173  // deleted.
   174  func (m *manifest) Transform() error {
   175  
   176  	sliced, err := toSlice(m.self[constPropApplications])
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	for _, app := range sliced {
   182  		appAsMap, err := toMap(app)
   183  		if err != nil {
   184  			return err
   185  		}
   186  
   187  		err = transformApp(appAsMap, m)
   188  		if err != nil {
   189  			return err
   190  		}
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func transformApp(app map[string]interface{}, m *manifest) error {
   197  
   198  	appName := "n/a"
   199  
   200  	if name, ok := app["name"].(string); ok {
   201  		if len(name) > 0 {
   202  			appName = name
   203  		}
   204  	}
   205  
   206  	if app[constPropBuildpacks] == nil {
   207  		// Revisit: not sure if a build pack is mandatory.
   208  		// In that case we should check that app.buildpack
   209  		// is present.
   210  		return nil
   211  	}
   212  
   213  	buildPacks, err := toSlice(app[constPropBuildpacks])
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	if len(buildPacks) > 1 {
   219  		return fmt.Errorf("More than one Cloud Foundry Buildpack is not supported. Please check manifest file '%s', application '%s'", m.name, appName)
   220  	}
   221  
   222  	if len(buildPacks) == 1 {
   223  		app[constPropBuildpack] = buildPacks[0]
   224  		delete(app, constPropBuildpacks)
   225  		m.modified = true
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  // IsModified ...
   232  func (m *manifest) IsModified() bool {
   233  	return m.modified
   234  }
   235  
   236  func toMap(i interface{}) (map[string]interface{}, error) {
   237  
   238  	if m, ok := i.(map[string]interface{}); ok {
   239  		return m, nil
   240  	}
   241  	return nil, fmt.Errorf("Failed to convert %v to map. Was %v", i, reflect.TypeOf(i))
   242  }
   243  
   244  func toSlice(i interface{}) ([]interface{}, error) {
   245  
   246  	if s, ok := i.([]interface{}); ok {
   247  		return s, nil
   248  	}
   249  	return nil, fmt.Errorf("Failed to convert %v to slice. Was %v", i, reflect.TypeOf(i))
   250  }