github.com/msaf1980/nfpmc@v0.1.6/cmd/nfpmc/packager.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  	"syscall"
    11  
    12  	"github.com/goreleaser/nfpm/v2"
    13  	_ "github.com/goreleaser/nfpm/v2/apk"
    14  	_ "github.com/goreleaser/nfpm/v2/deb"
    15  	"github.com/goreleaser/nfpm/v2/files"
    16  	_ "github.com/goreleaser/nfpm/v2/rpm"
    17  )
    18  
    19  const (
    20  	defaultStr = ""
    21  	configStr  = "config|noreplace"
    22  	symlinkStr = "symlink"
    23  	docStr     = "doc"
    24  )
    25  
    26  type InputType uint8
    27  
    28  const (
    29  	INPUT_DIR InputType = iota
    30  )
    31  
    32  var inputTypeStr = []string{"dir"}
    33  
    34  func (i *InputType) Set(value string) error {
    35  	switch strings.ToLower(value) {
    36  	case "dir":
    37  		*i = INPUT_DIR
    38  	default:
    39  		return fmt.Errorf("unknown input type")
    40  	}
    41  	return nil
    42  }
    43  
    44  func (i *InputType) String() string {
    45  	return inputTypeStr[*i]
    46  }
    47  
    48  func (i *InputType) Type() string {
    49  	return "intput_type"
    50  }
    51  
    52  type OutputType uint8
    53  
    54  const (
    55  	RPM OutputType = iota
    56  	DEB
    57  	APK
    58  )
    59  
    60  var outputTypeStr = []string{"rpm", "deb", "apk"}
    61  
    62  func (i *OutputType) Set(value string) error {
    63  	switch strings.ToLower(value) {
    64  	case "rpm":
    65  		*i = RPM
    66  	case "deb":
    67  		*i = DEB
    68  	case "apk":
    69  		*i = APK
    70  	default:
    71  		return fmt.Errorf("unknown output type")
    72  	}
    73  	return nil
    74  }
    75  
    76  func (i *OutputType) String() string {
    77  	return outputTypeStr[*i]
    78  }
    79  
    80  func (i *OutputType) Type() string {
    81  	return "output_type"
    82  }
    83  
    84  type StringSlice []string
    85  
    86  func (u *StringSlice) Set(value string) error {
    87  	*u = append(*u, value)
    88  	return nil
    89  }
    90  
    91  func (u *StringSlice) String() string {
    92  	return "[ " + strings.Join(*u, ", ") + " ]"
    93  }
    94  
    95  func (u *StringSlice) Type() string {
    96  	return "[]string"
    97  }
    98  
    99  type StringMap map[string]string
   100  
   101  func (u *StringMap) Set(key, value string) error {
   102  	(*u)[key] = value
   103  	return nil
   104  }
   105  
   106  func (u *StringMap) String() string {
   107  	var sb strings.Builder
   108  	sb.WriteString("[ ")
   109  	first := true
   110  	for k, v := range *u {
   111  		if first {
   112  			first = false
   113  		} else {
   114  			sb.WriteString(", ")
   115  		}
   116  		sb.WriteString(k)
   117  		sb.WriteString(" = ")
   118  		sb.WriteString(v)
   119  	}
   120  	sb.WriteString(" ]")
   121  	return sb.String()
   122  }
   123  
   124  func (u *StringMap) Type() string {
   125  	return "map[string]string"
   126  }
   127  
   128  // type FileMap struct {
   129  // 	Src string
   130  // 	Dst string
   131  // }
   132  
   133  type FileContentMap map[string]*files.Content
   134  
   135  type Packager struct {
   136  	InputType  InputType
   137  	OutputType OutputType
   138  	OutDir     string
   139  	OutName    string
   140  
   141  	Info        nfpm.Info
   142  	Compression string
   143  	PostUpgrade string
   144  	PreUpgrade  string
   145  
   146  	FilesMap FileContentMap
   147  }
   148  
   149  func charsToString(ca []int8) string {
   150  	s := make([]byte, len(ca))
   151  	var lens int
   152  	for ; lens < len(ca); lens++ {
   153  		if ca[lens] == 0 {
   154  			break
   155  		}
   156  		s[lens] = uint8(ca[lens])
   157  	}
   158  	return string(s[0:lens])
   159  }
   160  
   161  func (p *Packager) Init() error {
   162  	if len(p.Info.Name) == 0 {
   163  		return fmt.Errorf("name not set")
   164  	}
   165  	if len(p.Info.Version) == 0 {
   166  		return fmt.Errorf("version not set")
   167  	}
   168  	if len(p.Info.Release) == 0 {
   169  		return fmt.Errorf("iteration not set")
   170  	}
   171  
   172  	if p.Info.Arch == "" {
   173  		var buf syscall.Utsname
   174  		err := syscall.Uname(&buf)
   175  		if err != nil {
   176  			return err
   177  		}
   178  		arch := charsToString(buf.Machine[:])
   179  		if arch == "x86_64" || arch == "amd64" {
   180  			if p.OutputType == RPM {
   181  				p.Info.Arch = "x86_64"
   182  			} else {
   183  				p.Info.Arch = "amd64"
   184  			}
   185  		}
   186  	}
   187  
   188  	p.FilesMap = make(FileContentMap)
   189  
   190  	return nil
   191  }
   192  
   193  func (p *Packager) Validate() error {
   194  	if p.Info.Release == "0" {
   195  		sv := strings.IndexAny(p.Info.Version, "-_")
   196  		if sv > 1 {
   197  			v := p.Info.Version
   198  			p.Info.Version = v[0:sv]
   199  			p.Info.Release = v[sv+1:]
   200  		}
   201  	}
   202  	if p.Info.Release == "" {
   203  		p.Info.Release = "1"
   204  	}
   205  
   206  	nfpm.WithDefaults(&p.Info)
   207  	return p.Info.Validate()
   208  }
   209  
   210  // func rewriteFileName(name string, filesMap StringMap) (string, error) {
   211  // 	for k, v := range filesMap {
   212  // 		if strings.HasPrefix(name, k) {
   213  // 			return v + name[len(k):], nil
   214  // 		}
   215  // 	}
   216  // 	return "", fmt.Errorf("can't rewrite %s", name)
   217  // }
   218  
   219  func isDir(name string) (bool, error) {
   220  	fi, err := os.Stat(name)
   221  	if err != nil {
   222  		return false, err
   223  	}
   224  	if fi.IsDir() {
   225  		return true, nil
   226  	}
   227  	return false, nil
   228  }
   229  
   230  func expandDir(dir string) ([]string, error) {
   231  	var files []string
   232  
   233  	fs, err := ioutil.ReadDir(dir)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	for _, f := range fs {
   239  		fName := path.Join(dir, f.Name())
   240  		if ok, err := isDir(fName); err != nil {
   241  			return nil, err
   242  		} else if ok {
   243  			if filesDir, err := expandDir(fName); err == nil {
   244  				files = append(files, filesDir...)
   245  			} else {
   246  				return nil, err
   247  			}
   248  		} else {
   249  			files = append(files, fName)
   250  		}
   251  	}
   252  
   253  	return files, nil
   254  }
   255  
   256  func expand(glob string) (string, []string, error) {
   257  	var files []string
   258  	var root string
   259  
   260  	fs, err := filepath.Glob(glob)
   261  	if err != nil {
   262  		return root, nil, err
   263  	}
   264  	if len(fs) > 0 {
   265  		if ok, err := isDir(fs[0]); err != nil {
   266  			return fs[0], nil, err
   267  		} else if ok {
   268  			if strings.HasSuffix(fs[0], "/") {
   269  				root = fs[0]
   270  			} else {
   271  				root = fs[0] + "/"
   272  			}
   273  		} else if len(fs) == 1 && glob == fs[0] {
   274  			root = fs[0]
   275  		} else {
   276  			root = path.Dir(fs[0]) + "/"
   277  		}
   278  	}
   279  
   280  	for _, file := range fs {
   281  		if ok, err := isDir(file); err != nil {
   282  			return root, nil, err
   283  		} else if ok {
   284  			if filesDir, err := expandDir(file); err == nil {
   285  				files = append(files, filesDir...)
   286  			} else {
   287  				return root, nil, err
   288  			}
   289  		} else {
   290  			files = append(files, file)
   291  		}
   292  	}
   293  	return root, files, nil
   294  }
   295  
   296  func (p *Packager) AddFiles(fileS StringSlice) error {
   297  	for _, f := range fileS {
   298  		fileRemap := strings.Split(f, "=")
   299  		if len(fileRemap) > 2 {
   300  			return fmt.Errorf("filemap is invalid: %s", f)
   301  		}
   302  
   303  		// var v string
   304  		// if len(fileRemap) > 1 {
   305  		// 	v = fileRemap[1]
   306  		// } else {
   307  		// 	v = "/" + fileRemap[0]
   308  		// }
   309  
   310  		root, fs, err := expand(fileRemap[0])
   311  		if err != nil {
   312  			return err
   313  		}
   314  		for _, file := range fs {
   315  			var dest string
   316  			if len(fileRemap) == 1 {
   317  				dest = "/" + file
   318  			} else {
   319  				dest = strings.Replace(file, root, fileRemap[1], 1)
   320  			}
   321  			if _, ok := p.FilesMap[dest]; ok {
   322  				return fmt.Errorf("filemap produce duplicate: %s", dest)
   323  			}
   324  			c := &files.Content{Source: file, Destination: dest, Type: defaultStr}
   325  			p.Info.Contents = append(p.Info.Contents, c)
   326  			p.FilesMap[dest] = c
   327  		}
   328  	}
   329  	return nil
   330  }
   331  
   332  func (p *Packager) AddSymlinks(fileS StringSlice) error {
   333  	for _, f := range fileS {
   334  		fileRemap := strings.Split(f, "=")
   335  		if len(fileRemap) != 2 {
   336  			return fmt.Errorf("symlink is invalid: %s", f)
   337  		}
   338  		if _, ok := p.FilesMap[fileRemap[1]]; ok {
   339  			return fmt.Errorf("symlink try to overwrite existing: %s", fileRemap[1])
   340  		}
   341  		c := &files.Content{Source: fileRemap[0], Destination: fileRemap[1], Type: symlinkStr}
   342  		p.Info.Contents = append(p.Info.Contents, c)
   343  		p.FilesMap[fileRemap[1]] = c
   344  	}
   345  	return nil
   346  }
   347  
   348  func (p *Packager) setFiles(filesSet StringSlice, typ string) error {
   349  	for _, f := range filesSet {
   350  		var dpath string
   351  		if strings.HasSuffix(f, "/") {
   352  			dpath = f
   353  		} else {
   354  			dpath = f + "/"
   355  		}
   356  		for k, v := range p.FilesMap {
   357  			if k == f || strings.HasPrefix(k, dpath) {
   358  				if v.Type == "" {
   359  					v.Type = typ
   360  				}
   361  			}
   362  		}
   363  	}
   364  	return nil
   365  }
   366  
   367  func (p *Packager) SetConfigFiles(filesSet StringSlice) error {
   368  	return p.setFiles(filesSet, configStr)
   369  }
   370  
   371  func (p *Packager) SetDocFiles(filesSet StringSlice) error {
   372  	return p.setFiles(filesSet, docStr)
   373  }
   374  
   375  func (p *Packager) formatOutName(packager nfpm.Packager) string {
   376  	s := p.OutName
   377  	if s == "" {
   378  		return packager.ConventionalFileName(&p.Info)
   379  	}
   380  
   381  	s = strings.ReplaceAll(s, "NAME", p.Info.Name)
   382  	s = strings.ReplaceAll(s, "VERSION", p.Info.Version)
   383  	s = strings.ReplaceAll(s, "ITERATION", p.Info.Release)
   384  	s = strings.ReplaceAll(s, "ARCH", p.Info.Arch)
   385  	s = strings.ReplaceAll(s, "PLATFORM", p.Info.Platform)
   386  
   387  	return s
   388  }
   389  
   390  func fileExists(filename string) bool {
   391  	info, err := os.Stat(filename)
   392  	if os.IsNotExist(err) {
   393  		return false
   394  	}
   395  	return !info.IsDir()
   396  }
   397  
   398  func (p *Packager) Do(overwrite bool) (string, error) {
   399  	packager, err := nfpm.Get(p.OutputType.String())
   400  	if err != nil {
   401  		return "", err
   402  	}
   403  
   404  	if len(p.PostUpgrade) > 0 {
   405  		p.Info.RPM.Scripts.PostTrans = p.PostUpgrade
   406  		p.Info.APK.Scripts.PostUpgrade = p.PostUpgrade
   407  	}
   408  	if len(p.PreUpgrade) > 0 {
   409  		p.Info.RPM.Scripts.PreTrans = p.PostUpgrade
   410  		p.Info.APK.Scripts.PreUpgrade = p.PostUpgrade
   411  	}
   412  
   413  	outName := p.formatOutName(packager)
   414  	if p.OutDir == "" {
   415  		// if no target was specified create a package in
   416  		// current directory with a conventional file name
   417  		p.Info.Target = outName
   418  	} else {
   419  		p.Info.Target = path.Join(p.OutDir, outName)
   420  	}
   421  
   422  	if fileExists(p.Info.Target) {
   423  		if overwrite {
   424  			if err := os.Remove(p.Info.Target); err != nil {
   425  				return p.Info.Target, err
   426  			}
   427  		} else {
   428  			return p.Info.Target, fmt.Errorf("%s already exist", p.Info.Target)
   429  		}
   430  	}
   431  
   432  	f, err := os.Create(p.Info.Target)
   433  	if err != nil {
   434  		return p.Info.Target, err
   435  	}
   436  
   437  	err = packager.Package(&p.Info, f)
   438  	if err != nil {
   439  		f.Close()
   440  		os.Remove(p.Info.Target)
   441  		return p.Info.Target, err
   442  	}
   443  
   444  	err = f.Close()
   445  	if err != nil {
   446  		os.Remove(p.Info.Target)
   447  	}
   448  
   449  	return p.Info.Target, nil
   450  }