main.go 6.6 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
)

Lukas Burgey's avatar
Lukas Burgey committed
57
var (
Lukas Burgey's avatar
Lukas Burgey committed
58
	client = &http.Client{}
Lukas Burgey's avatar
Lukas Burgey committed
59
60
61
62
63
64
	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
65
		"0.3.0",
Lukas Burgey's avatar
Lukas Burgey committed
66
	)
Lukas Burgey's avatar
Lukas Burgey committed
67
68
69
	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()
70
	brokerDebugging  = app.Flag("debug-broker", "Display debugging info concerning the message broker").Bool()
Lukas Burgey's avatar
Lukas Burgey committed
71
	debugAll         = app.Flag("debug", "Display all debugging info").Bool()
Lukas Burgey's avatar
Lukas Burgey committed
72
73
)

74
func logError(err error, msg string) {
Lukas Burgey's avatar
Lukas Burgey committed
75
	if err != nil {
76
		log.Printf("[E] %s: %s", msg, err)
Lukas Burgey's avatar
Lukas Burgey committed
77
78
79
	}
}

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
func (c *config) syncConfig() (err error) {
	// we inform the backend which services we provide
	strippedConfigBytes, err := json.Marshal(strippedConfig{
		Services:        c.Services,
		GroupToServices: c.GroupToServices,
	})
	if err != nil {
		return
	}

	// update the services tracked by the backend
	req, err := http.NewRequest(
		"PUT",
		"https://"+c.Host+"/backend/clientapi/config",
		bytes.NewReader(strippedConfigBytes),
	)
Lukas Burgey's avatar
Lukas Burgey committed
96
97
98
99
	if err != nil {
		return
	}

100
	req.SetBasicAuth(c.Username, c.Password)
101
	req.Header.Set("Content-Type", "application/json")
Lukas Burgey's avatar
Lukas Burgey committed
102
103
104
105
106
107
108
109
110
111
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
112
113
114
115
116
117

	if resp.StatusCode != 200 {
		err = fmt.Errorf("Unable to sync configuration (backend returned %v)", resp.Status)
		return
	}

Lukas Burgey's avatar
Lukas Burgey committed
118
	var fetchedConfig fetchedConfig
119
	err = json.Unmarshal(body, &fetchedConfig)
Lukas Burgey's avatar
Lukas Burgey committed
120
	if err != nil {
121
		log.Fatalf("Unable to parse: %s %s", err, body)
Lukas Burgey's avatar
Lukas Burgey committed
122
123
		return
	}
124

Lukas Burgey's avatar
Lukas Burgey committed
125
126
	c.RabbitMQConfig = fetchedConfig.RabbitMQConfig
	c.Site = fetchedConfig.Site
127

128
129
130
131
132
133
134
135
136
	// we *disabled* service filtering as clients can push the services to the backend
	/*
		permittedServices := fetchedConfig.Services
		log.Printf("[Conf] Permitted services: %s", permittedServices)
			if len(c.Services) == 0 {
				log.Printf("[Conf] Config specifies no services. Using all permitted services")
			} else {
				// if the config specifies services we check if they are all permitted
				c.Services = filterPermitted(permittedServices, c.Services)
137

138
139
			}
	*/
140

141
	log.Printf("[Conf] Synchronised with backend")
Lukas Burgey's avatar
Lukas Burgey committed
142

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

Lukas Burgey's avatar
Lukas Burgey committed
146
func filterPermitted(permitted []service, wanted []service) (remainder []service) {
Lukas Burgey's avatar
Lukas Burgey committed
147
148
149
150
serviceLoop:
	for _, service := range wanted {
		for _, permittedService := range permitted {
			if service.Name == permittedService.Name {
Lukas Burgey's avatar
Lukas Burgey committed
151
				remainder = append(remainder, service)
Lukas Burgey's avatar
Lukas Burgey committed
152
153
154
155
156
157
158
159
				continue serviceLoop
			}
		}
		log.Printf("[Conn] Skipping unpermitted service %s", service)
	}
	return
}

160
func getConfig(configFile string) (c config, err error) {
161
162
163

	log.Printf("[Conf] Reading config file %s", configFile)

Lukas Burgey's avatar
Lukas Burgey committed
164
	bs, err := ioutil.ReadFile(configFile)
165
166
167
168
169
	if err != nil {
		log.Printf("[Conf] Error reading config file: %s", err)
		return
	}

Lukas Burgey's avatar
Lukas Burgey committed
170
171
	err = json.Unmarshal(bs, &c)
	if err != nil {
172
173
174
		log.Printf("[Conf] Error parsing config file: %s", err)
		return
	}
Lukas Burgey's avatar
Lukas Burgey committed
175

176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
	// 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 == "" {
		log.Fatalf("[Conf] No 'fetch_interval' in config")
	}
	if c.ReconnectTimeoutString == "" {
		log.Fatalf("[Conf] No 'reconnect_timeout' in config")
Lukas Burgey's avatar
Lukas Burgey committed
191
	}
192
193

	// parse som of the config values
194
195
196
197
198
199
200
201
	if c.FetchInterval, err = time.ParseDuration(c.FetchIntervalString); err != nil {
		log.Printf("[Conf] Error parsing fetch interval: %s", err)
		return
	}
	if c.ReconnectTimeout, err = time.ParseDuration(c.ReconnectTimeoutString); err != nil {
		log.Printf("[Conf] Error parsing reconnect timeout: %s", err)
		return
	}
202
203

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

210
211
212
	log.Printf("[Conf] Services: %s", c.Services)
	log.Printf("[Conf] Groups: %s", c.GroupToServices)

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

Lukas Burgey's avatar
Lukas Burgey committed
217
218
	return
}
Lukas Burgey's avatar
Lukas Burgey committed
219
220

func main() {
Lukas Burgey's avatar
Lukas Burgey committed
221
	var err error
Lukas Burgey's avatar
Lukas Burgey committed
222

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

Lukas Burgey's avatar
Lukas Burgey committed
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
	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
242
	// read the config file
243
	c, err := getConfig(*configFile)
244
245
246
247
	if err != nil {
		log.Fatalf("[Err] No valid config. Exiting")
	}

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

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

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

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

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

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