]> ruderich.org/simon Gitweb - nsscash/nsscash.git/blob - main.go
.github: update upstream actions to latest version
[nsscash/nsscash.git] / main.go
1 // Main file for nsscash
2
3 // Copyright (C) 2019-2021  Simon Ruderich
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU Affero General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU Affero General Public License for more details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18 package main
19
20 import (
21         "bytes"
22         "flag"
23         "fmt"
24         "io/ioutil"
25         "log"
26         "os"
27         "path/filepath"
28
29         "github.com/google/renameio"
30 )
31
32 func main() {
33         flag.Usage = func() {
34                 fmt.Fprintf(os.Stderr,
35                         "usage: %[1]s [options] fetch <config>\n"+
36                                 "usage: %[1]s [options] convert <type> <src> <dst>\n"+
37                                 "",
38                         os.Args[0])
39                 flag.PrintDefaults()
40         }
41         flag.Parse()
42
43         args := flag.Args()
44         if len(args) == 0 {
45                 flag.Usage()
46                 os.Exit(1)
47         }
48
49         switch args[0] {
50         case "fetch":
51                 if len(args) != 2 {
52                         break
53                 }
54
55                 err := mainFetch(args[1])
56                 if err != nil {
57                         log.Fatal(err)
58                 }
59                 return
60
61         case "convert":
62                 if len(args) != 4 {
63                         break
64                 }
65
66                 err := mainConvert(args[1], args[2], args[3])
67                 if err != nil {
68                         log.Fatal(err)
69                 }
70                 return
71         }
72
73         flag.Usage()
74         os.Exit(1)
75 }
76
77 func mainFetch(cfgPath string) error {
78         cfg, err := LoadConfig(cfgPath)
79         if err != nil {
80                 return err
81         }
82         state, err := LoadState(cfg.StatePath)
83         if err != nil {
84                 return err
85         }
86         err = handleFiles(cfg, state)
87         if err != nil {
88                 return err
89         }
90         // NOTE: Make sure to call WriteState() only if there were no
91         // errors (see WriteState() and README)
92         err = WriteState(cfg.StatePath, state)
93         if err != nil {
94                 return err
95         }
96         return nil
97 }
98
99 func mainConvert(typ, srcPath, dstPath string) error {
100         var t FileType
101         err := t.UnmarshalText([]byte(typ))
102         if err != nil {
103                 return err
104         }
105
106         src, err := ioutil.ReadFile(srcPath)
107         if err != nil {
108                 return err
109         }
110         var x bytes.Buffer
111         if t == FileTypePlain {
112                 x.Write(src)
113         } else if t == FileTypePasswd {
114                 pws, err := ParsePasswds(bytes.NewReader(src))
115                 if err != nil {
116                         return err
117                 }
118                 err = SerializePasswds(&x, pws)
119                 if err != nil {
120                         return err
121                 }
122         } else if t == FileTypeGroup {
123                 grs, err := ParseGroups(bytes.NewReader(src))
124                 if err != nil {
125                         return err
126                 }
127                 err = SerializeGroups(&x, grs)
128                 if err != nil {
129                         return err
130                 }
131         } else {
132                 return fmt.Errorf("unsupported file type %v", t)
133         }
134
135         // We must create the file first or deployFile() will abort; this is
136         // ugly because deployFile() already performs an atomic replacement
137         // but the simplest solution with the least duplicate code
138         f, err := renameio.TempFile(filepath.Dir(dstPath), dstPath)
139         if err != nil {
140                 return err
141         }
142         defer f.Cleanup()
143
144         err = deployFile(&File{
145                 Type: t,
146                 Url:  srcPath,
147                 Path: f.Name(),
148                 body: x.Bytes(),
149         })
150         if err != nil {
151                 return err
152         }
153
154         err = f.CloseAtomicallyReplace()
155         if err != nil {
156                 return err
157         }
158         return syncPath(filepath.Dir(dstPath))
159 }