main.go 6.56 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1
2
3
package main

import (
4
	"bytes"
Lukas Burgey's avatar
Lukas Burgey committed
5
	"encoding/json"
6
	"fmt"
Lukas Burgey's avatar
Lukas Burgey committed
7
8
9
	"io/ioutil"
	"log"
	"net/http"
Lukas Burgey's avatar
Lukas Burgey committed
10
	"os"
11
	"time"
Lukas Burgey's avatar
Lukas Burgey committed
12

Lukas Burgey's avatar
Lukas Burgey committed
13
	"gopkg.in/alecthomas/kingpin.v2"
Lukas Burgey's avatar
Lukas Burgey committed
14
15
)

Lukas Burgey's avatar
Lukas Burgey committed
16
17
type (
	rabbitMQConfig struct {
Lukas Burgey's avatar
Lukas Burgey committed
18
19
		Exchanges []string `json:"exchanges"`
		Vhost     string   `json:"vhost"`
Lukas Burgey's avatar
Lukas Burgey committed
20
21
22
23
	}

	fetchedConfig struct {
		RabbitMQConfig rabbitMQConfig `json:"rabbitmq_config"`
Lukas Burgey's avatar
Lukas Burgey committed
24
		Site           string         `json:"site"`
Lukas Burgey's avatar
Lukas Burgey committed
25
26
27
	}

	config struct {
Lukas Burgey's avatar
Lukas Burgey committed
28
29
30
31
32
33
34
35
36
37
		Host     string `json:"host"`
		Username string `json:"username"`
		Password string `json:"password"`

		// Services provided at the site of this client
		// this is for  deployment per _service_
		Services []service `json:"services"`

		// GroupToServices maps a group name to services provided for this group
		// this is for  deployment per _group_
Lukas Burgey's avatar
Lukas Burgey committed
38
		GroupToServices map[string]([]service) `json:"group_to_services"`
Lukas Burgey's avatar
Lukas Burgey committed
39
40
41

		FetchIntervalString    string `json:"fetch_interval"`    // string parsed by time.ParseDuration
		ReconnectTimeoutString string `json:"reconnect_timeout"` // string parsed by time.ParseDuration
Lukas Burgey's avatar
Lukas Burgey committed
42
		NewTasks               chan task
43
		DoneTasks              chan taskExecution
Lukas Burgey's avatar
Lukas Burgey committed
44
45
		FetchInterval          time.Duration
		ReconnectTimeout       time.Duration
Lukas Burgey's avatar
Lukas Burgey committed
46
47
		RabbitMQConfig         rabbitMQConfig
		Site                   string
Lukas Burgey's avatar
Lukas Burgey committed
48
	}
49
50
51
52
53
54

	// strippedConfig is sent to the backend on startup
	strippedConfig struct {
		Services        []service              `json:"services"`
		GroupToServices map[string]([]service) `json:"group_to_services"`
	}
Lukas Burgey's avatar
Lukas Burgey committed
55
56
)

57
58
59
60
61
const (
	defaultFetchInterval    = 30 * time.Minute
	defaultReconnectTimeout = 10 * time.Second
)

Lukas Burgey's avatar
Lukas Burgey committed
62
var (
Lukas Burgey's avatar
Lukas Burgey committed
63
	client = &http.Client{}
Lukas Burgey's avatar
Lukas Burgey committed
64
65
66
67
68
69
	app    = kingpin.New(
		"FEUDAL Client",
		"Client for the Federated User Credential Deployment Portal (FEUDAL)",
	).Author(
		"Lukas Burgey",
	).Version(
Lukas Burgey's avatar
Lukas Burgey committed
70
		"0.3.0",
Lukas Burgey's avatar
Lukas Burgey committed
71
	)
Lukas Burgey's avatar
Lukas Burgey committed
72
73
74
75
76
77
	configFile          = app.Arg("config", "Config file to file to use.").Required().String()
	scriptDebugging     = app.Flag("debug-scripts", "Display debugging info concerning executed scripts").Bool()
	backendDebugging    = app.Flag("debug-backend", "Display debugging info concerning the backend").Bool()
	brokerDebugging     = app.Flag("debug-broker", "Display debugging info concerning the message broker").Bool()
	debugAll            = app.Flag("debug", "Display all debugging info").Bool()
	sequentialExecution = app.Flag("seq", "Execute tasks sequentially").Bool()
Lukas Burgey's avatar
Lukas Burgey committed
78
79
)

80
func logError(err error, msg string) {
Lukas Burgey's avatar
Lukas Burgey committed
81
	if err != nil {
82
		log.Printf("[E] %s: %s", msg, err)
Lukas Burgey's avatar
Lukas Burgey committed
83
84
85
	}
}

86
func (c *config) syncConfig() (err error) {
Lukas Burgey's avatar
Lukas Burgey committed
87
88
89
90
91
92
93
94
95
96

	log.Printf("[Conf] Synchronising configuration with %v", c.Host)

	var (
		strippedConfigBytes []byte
		req                 *http.Request
		resp                *http.Response
		fetchedConfig       fetchedConfig
	)

97
	// we inform the backend which services we provide
Lukas Burgey's avatar
Lukas Burgey committed
98
	strippedConfigBytes, err = json.Marshal(strippedConfig{
99
100
101
102
103
104
105
106
		Services:        c.Services,
		GroupToServices: c.GroupToServices,
	})
	if err != nil {
		return
	}

	// update the services tracked by the backend
Lukas Burgey's avatar
Lukas Burgey committed
107
	req, err = http.NewRequest(
108
109
110
111
		"PUT",
		"https://"+c.Host+"/backend/clientapi/config",
		bytes.NewReader(strippedConfigBytes),
	)
Lukas Burgey's avatar
Lukas Burgey committed
112
113
114
115
	if err != nil {
		return
	}

116
	req.SetBasicAuth(c.Username, c.Password)
117
	req.Header.Set("Content-Type", "application/json")
Lukas Burgey's avatar
Lukas Burgey committed
118
	resp, err = client.Do(req)
Lukas Burgey's avatar
Lukas Burgey committed
119
120
121
122
123
124
125
126
127
	if err != nil {
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
128
	if resp.StatusCode != 200 {
Lukas Burgey's avatar
Lukas Burgey committed
129
		err = fmt.Errorf("Unable to sync configuration (response: %v)", resp.Status)
130
131
132
		return
	}

133
	err = json.Unmarshal(body, &fetchedConfig)
Lukas Burgey's avatar
Lukas Burgey committed
134
	if err != nil {
Lukas Burgey's avatar
Lukas Burgey committed
135
		err = fmt.Errorf("Unable to parse remote configuration: %s %s", err, body)
Lukas Burgey's avatar
Lukas Burgey committed
136
137
		return
	}
138

Lukas Burgey's avatar
Lukas Burgey committed
139
140
	c.RabbitMQConfig = fetchedConfig.RabbitMQConfig
	c.Site = fetchedConfig.Site
141

Lukas Burgey's avatar
Lukas Burgey committed
142
143
	log.Printf("[Conf] Synchronised configuration with %v", c.Host)

Lukas Burgey's avatar
Lukas Burgey committed
144
145
146
	return
}

147
func getConfig(configFile string) (c config, err error) {
148

Lukas Burgey's avatar
Lukas Burgey committed
149
	c.Log("Reading config file %s", configFile)
150

Lukas Burgey's avatar
Lukas Burgey committed
151
	bs, err := ioutil.ReadFile(configFile)
152
	if err != nil {
Lukas Burgey's avatar
Lukas Burgey committed
153
		c.Log("Error reading config file: %s", err)
154
155
156
		return
	}

Lukas Burgey's avatar
Lukas Burgey committed
157
158
	err = json.Unmarshal(bs, &c)
	if err != nil {
Lukas Burgey's avatar
Lukas Burgey committed
159
		c.Log("Error parsing config file: %s", err)
160
161
		return
	}
Lukas Burgey's avatar
Lukas Burgey committed
162

163
164
165
166
167
168
169
170
171
172
173
	// check the config values
	if c.Host == "" {
		log.Fatalf("[Conf] No 'host' in config")
	}
	if c.Username == "" {
		log.Fatalf("[Conf] No 'user' in config")
	}
	if c.Password == "" {
		log.Fatalf("[Conf] No 'password' in config")
	}
	if c.FetchIntervalString == "" {
174
175
176
177
178
179
180
		c.FetchInterval = defaultFetchInterval
		log.Printf("[Conf] Using default fetch_interval of %v", c.FetchInterval)
	} else {
		c.FetchInterval, err = time.ParseDuration(c.FetchIntervalString)
		if err != nil {
			log.Printf("[Conf] Error parsing fetch interval: %s", err)
			err = nil
181

182
183
184
			c.FetchInterval = defaultFetchInterval
			log.Printf("[Conf] Using default fetch_interval of %v", c.FetchInterval)
		}
185
	}
186
187
188
189
190
191
192
193
194
195
196
197
198

	if c.ReconnectTimeoutString == "" {
		c.ReconnectTimeout = defaultReconnectTimeout
		log.Printf("[Conf] Using default reconnect_timeout of %v", c.ReconnectTimeout)
	} else {
		c.ReconnectTimeout, err = time.ParseDuration(c.ReconnectTimeoutString)
		if err != nil {
			log.Printf("[Conf] Error parsing reconnect timeout: %s", err)
			err = nil

			c.ReconnectTimeout = defaultReconnectTimeout
			log.Printf("[Conf] Using default reconnect_timeout of %v", c.ReconnectTimeout)
		}
199
	}
200
201

	// fetch the remote configuration
202
	err = c.syncConfig()
203
	if err != nil {
Lukas Burgey's avatar
Lukas Burgey committed
204
		log.Printf("[Conf] Error fetching remote config: %s", err)
205
206
		return
	}
Lukas Burgey's avatar
Lukas Burgey committed
207

Lukas Burgey's avatar
Lukas Burgey committed
208
209
	c.Log("Services: %s", c.Services)
	c.Log("Groups: %s", c.GroupToServices)
210

Lukas Burgey's avatar
Lukas Burgey committed
211
212
	// initialize the task queues
	c.NewTasks = make(chan task)
213
	c.DoneTasks = make(chan taskExecution)
Lukas Burgey's avatar
Lukas Burgey committed
214

Lukas Burgey's avatar
Lukas Burgey committed
215
216
	return
}
Lukas Burgey's avatar
Lukas Burgey committed
217
218

func main() {
Lukas Burgey's avatar
Lukas Burgey committed
219
	var err error
Lukas Burgey's avatar
Lukas Burgey committed
220

Lukas Burgey's avatar
Lukas Burgey committed
221
	// get arguments
Lukas Burgey's avatar
Lukas Burgey committed
222
223
	kingpin.MustParse(app.Parse(os.Args[1:]))

Lukas Burgey's avatar
Lukas Burgey committed
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
	if *debugAll {
		*brokerDebugging = true
		*scriptDebugging = true
		*backendDebugging = true
	}

	if *brokerDebugging {
		log.Printf("[Debug] broker debugging enabled")
	}
	if *scriptDebugging {
		log.Printf("[Debug] script debugging enabled")
	}
	if *backendDebugging {
		log.Printf("[Debug] backend debugging enabled")
	}
Lukas Burgey's avatar
Lukas Burgey committed
239
240
241
	if *sequentialExecution {
		log.Printf("[Debug] Executing tasks sequentially")
	}
Lukas Burgey's avatar
Lukas Burgey committed
242

Lukas Burgey's avatar
Lukas Burgey committed
243
	// read the config file
244
	c, err := getConfig(*configFile)
245
	if err != nil {
Lukas Burgey's avatar
Lukas Burgey committed
246
		log.Fatalf("[Exit] No valid config. Exiting")
247
248
	}

Lukas Burgey's avatar
Lukas Burgey committed
249
	if len(c.Services) == 0 && len(c.GroupToServices) == 0 {
Lukas Burgey's avatar
Lukas Burgey committed
250
251
		log.Printf("[P] Not starting pubsub because the are no services to subscribe to")
		return
Lukas Burgey's avatar
Lukas Burgey committed
252
253
	}

254
	// start task handler and responder
255
	go c.taskHandler()
256
	go c.taskResponder()
257

Lukas Burgey's avatar
Lukas Burgey committed
258
	consumer := c.consumer()
259
	defer consumer.close()
Lukas Burgey's avatar
Lukas Burgey committed
260

Lukas Burgey's avatar
Lukas Burgey committed
261
	consumer.startConsuming()
Lukas Burgey's avatar
Lukas Burgey committed
262

Lukas Burgey's avatar
Lukas Burgey committed
263
264
265
	// start the fetcher after the consuming starts
	// -> we miss nothing
	go c.taskFetcher()
Lukas Burgey's avatar
Lukas Burgey committed
266

Lukas Burgey's avatar
Lukas Burgey committed
267
268
269
	// run till killed
	forever := make(chan bool)
	<-forever
Lukas Burgey's avatar
Lukas Burgey committed
270
}