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 }