1/*
2Package alils implements the SDK(v0.5.0) of Simple Log Service(abbr. SLS).
3
4For more description about SLS, please read this article:
5http://gitlab.alibaba-inc.com/sls/doc.
6*/
7package alils
8
9import (
10	"encoding/json"
11	"fmt"
12	"io/ioutil"
13	"net/http"
14	"net/http/httputil"
15)
16
17// Error message in SLS HTTP response.
18type errorMessage struct {
19	Code    string `json:"errorCode"`
20	Message string `json:"errorMessage"`
21}
22
23// LogProject Define the Ali Project detail
24type LogProject struct {
25	Name            string // Project name
26	Endpoint        string // IP or hostname of SLS endpoint
27	AccessKeyID     string
28	AccessKeySecret string
29}
30
31// NewLogProject creates a new SLS project.
32func NewLogProject(name, endpoint, AccessKeyID, accessKeySecret string) (p *LogProject, err error) {
33	p = &LogProject{
34		Name:            name,
35		Endpoint:        endpoint,
36		AccessKeyID:     AccessKeyID,
37		AccessKeySecret: accessKeySecret,
38	}
39	return p, nil
40}
41
42// ListLogStore returns all logstore names of project p.
43func (p *LogProject) ListLogStore() (storeNames []string, err error) {
44	h := map[string]string{
45		"x-sls-bodyrawsize": "0",
46	}
47
48	uri := fmt.Sprintf("/logstores")
49	r, err := request(p, "GET", uri, h, nil)
50	if err != nil {
51		return
52	}
53
54	buf, err := ioutil.ReadAll(r.Body)
55	if err != nil {
56		return
57	}
58
59	if r.StatusCode != http.StatusOK {
60		errMsg := &errorMessage{}
61		err = json.Unmarshal(buf, errMsg)
62		if err != nil {
63			err = fmt.Errorf("failed to list logstore")
64			dump, _ := httputil.DumpResponse(r, true)
65			fmt.Printf("%s\n", dump)
66			return
67		}
68		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
69		return
70	}
71
72	type Body struct {
73		Count     int
74		LogStores []string
75	}
76	body := &Body{}
77
78	err = json.Unmarshal(buf, body)
79	if err != nil {
80		return
81	}
82
83	storeNames = body.LogStores
84
85	return
86}
87
88// GetLogStore returns logstore according by logstore name.
89func (p *LogProject) GetLogStore(name string) (s *LogStore, err error) {
90	h := map[string]string{
91		"x-sls-bodyrawsize": "0",
92	}
93
94	r, err := request(p, "GET", "/logstores/"+name, h, nil)
95	if err != nil {
96		return
97	}
98
99	buf, err := ioutil.ReadAll(r.Body)
100	if err != nil {
101		return
102	}
103
104	if r.StatusCode != http.StatusOK {
105		errMsg := &errorMessage{}
106		err = json.Unmarshal(buf, errMsg)
107		if err != nil {
108			err = fmt.Errorf("failed to get logstore")
109			dump, _ := httputil.DumpResponse(r, true)
110			fmt.Printf("%s\n", dump)
111			return
112		}
113		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
114		return
115	}
116
117	s = &LogStore{}
118	err = json.Unmarshal(buf, s)
119	if err != nil {
120		return
121	}
122	s.project = p
123	return
124}
125
126// CreateLogStore creates a new logstore in SLS,
127// where name is logstore name,
128// and ttl is time-to-live(in day) of logs,
129// and shardCnt is the number of shards.
130func (p *LogProject) CreateLogStore(name string, ttl, shardCnt int) (err error) {
131
132	type Body struct {
133		Name       string `json:"logstoreName"`
134		TTL        int    `json:"ttl"`
135		ShardCount int    `json:"shardCount"`
136	}
137
138	store := &Body{
139		Name:       name,
140		TTL:        ttl,
141		ShardCount: shardCnt,
142	}
143
144	body, err := json.Marshal(store)
145	if err != nil {
146		return
147	}
148
149	h := map[string]string{
150		"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
151		"Content-Type":      "application/json",
152		"Accept-Encoding":   "deflate", // TODO: support lz4
153	}
154
155	r, err := request(p, "POST", "/logstores", h, body)
156	if err != nil {
157		return
158	}
159
160	body, err = ioutil.ReadAll(r.Body)
161	if err != nil {
162		return
163	}
164
165	if r.StatusCode != http.StatusOK {
166		errMsg := &errorMessage{}
167		err = json.Unmarshal(body, errMsg)
168		if err != nil {
169			err = fmt.Errorf("failed to create logstore")
170			dump, _ := httputil.DumpResponse(r, true)
171			fmt.Printf("%s\n", dump)
172			return
173		}
174		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
175		return
176	}
177
178	return
179}
180
181// DeleteLogStore deletes a logstore according by logstore name.
182func (p *LogProject) DeleteLogStore(name string) (err error) {
183	h := map[string]string{
184		"x-sls-bodyrawsize": "0",
185	}
186
187	r, err := request(p, "DELETE", "/logstores/"+name, h, nil)
188	if err != nil {
189		return
190	}
191
192	body, err := ioutil.ReadAll(r.Body)
193	if err != nil {
194		return
195	}
196
197	if r.StatusCode != http.StatusOK {
198		errMsg := &errorMessage{}
199		err = json.Unmarshal(body, errMsg)
200		if err != nil {
201			err = fmt.Errorf("failed to delete logstore")
202			dump, _ := httputil.DumpResponse(r, true)
203			fmt.Printf("%s\n", dump)
204			return
205		}
206		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
207		return
208	}
209	return
210}
211
212// UpdateLogStore updates a logstore according by logstore name,
213// obviously we can't modify the logstore name itself.
214func (p *LogProject) UpdateLogStore(name string, ttl, shardCnt int) (err error) {
215
216	type Body struct {
217		Name       string `json:"logstoreName"`
218		TTL        int    `json:"ttl"`
219		ShardCount int    `json:"shardCount"`
220	}
221
222	store := &Body{
223		Name:       name,
224		TTL:        ttl,
225		ShardCount: shardCnt,
226	}
227
228	body, err := json.Marshal(store)
229	if err != nil {
230		return
231	}
232
233	h := map[string]string{
234		"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
235		"Content-Type":      "application/json",
236		"Accept-Encoding":   "deflate", // TODO: support lz4
237	}
238
239	r, err := request(p, "PUT", "/logstores", h, body)
240	if err != nil {
241		return
242	}
243
244	body, err = ioutil.ReadAll(r.Body)
245	if err != nil {
246		return
247	}
248
249	if r.StatusCode != http.StatusOK {
250		errMsg := &errorMessage{}
251		err = json.Unmarshal(body, errMsg)
252		if err != nil {
253			err = fmt.Errorf("failed to update logstore")
254			dump, _ := httputil.DumpResponse(r, true)
255			fmt.Printf("%s\n", dump)
256			return
257		}
258		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
259		return
260	}
261
262	return
263}
264
265// ListMachineGroup returns machine group name list and the total number of machine groups.
266// The offset starts from 0 and the size is the max number of machine groups could be returned.
267func (p *LogProject) ListMachineGroup(offset, size int) (m []string, total int, err error) {
268	h := map[string]string{
269		"x-sls-bodyrawsize": "0",
270	}
271
272	if size <= 0 {
273		size = 500
274	}
275
276	uri := fmt.Sprintf("/machinegroups?offset=%v&size=%v", offset, size)
277	r, err := request(p, "GET", uri, h, nil)
278	if err != nil {
279		return
280	}
281
282	buf, err := ioutil.ReadAll(r.Body)
283	if err != nil {
284		return
285	}
286
287	if r.StatusCode != http.StatusOK {
288		errMsg := &errorMessage{}
289		err = json.Unmarshal(buf, errMsg)
290		if err != nil {
291			err = fmt.Errorf("failed to list machine group")
292			dump, _ := httputil.DumpResponse(r, true)
293			fmt.Printf("%s\n", dump)
294			return
295		}
296		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
297		return
298	}
299
300	type Body struct {
301		MachineGroups []string
302		Count         int
303		Total         int
304	}
305	body := &Body{}
306
307	err = json.Unmarshal(buf, body)
308	if err != nil {
309		return
310	}
311
312	m = body.MachineGroups
313	total = body.Total
314
315	return
316}
317
318// GetMachineGroup retruns machine group according by machine group name.
319func (p *LogProject) GetMachineGroup(name string) (m *MachineGroup, err error) {
320	h := map[string]string{
321		"x-sls-bodyrawsize": "0",
322	}
323
324	r, err := request(p, "GET", "/machinegroups/"+name, h, nil)
325	if err != nil {
326		return
327	}
328
329	buf, err := ioutil.ReadAll(r.Body)
330	if err != nil {
331		return
332	}
333
334	if r.StatusCode != http.StatusOK {
335		errMsg := &errorMessage{}
336		err = json.Unmarshal(buf, errMsg)
337		if err != nil {
338			err = fmt.Errorf("failed to get machine group:%v", name)
339			dump, _ := httputil.DumpResponse(r, true)
340			fmt.Printf("%s\n", dump)
341			return
342		}
343		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
344		return
345	}
346
347	m = &MachineGroup{}
348	err = json.Unmarshal(buf, m)
349	if err != nil {
350		return
351	}
352	m.project = p
353	return
354}
355
356// CreateMachineGroup creates a new machine group in SLS.
357func (p *LogProject) CreateMachineGroup(m *MachineGroup) (err error) {
358
359	body, err := json.Marshal(m)
360	if err != nil {
361		return
362	}
363
364	h := map[string]string{
365		"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
366		"Content-Type":      "application/json",
367		"Accept-Encoding":   "deflate", // TODO: support lz4
368	}
369
370	r, err := request(p, "POST", "/machinegroups", h, body)
371	if err != nil {
372		return
373	}
374
375	body, err = ioutil.ReadAll(r.Body)
376	if err != nil {
377		return
378	}
379
380	if r.StatusCode != http.StatusOK {
381		errMsg := &errorMessage{}
382		err = json.Unmarshal(body, errMsg)
383		if err != nil {
384			err = fmt.Errorf("failed to create machine group")
385			dump, _ := httputil.DumpResponse(r, true)
386			fmt.Printf("%s\n", dump)
387			return
388		}
389		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
390		return
391	}
392
393	return
394}
395
396// UpdateMachineGroup updates a machine group.
397func (p *LogProject) UpdateMachineGroup(m *MachineGroup) (err error) {
398
399	body, err := json.Marshal(m)
400	if err != nil {
401		return
402	}
403
404	h := map[string]string{
405		"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
406		"Content-Type":      "application/json",
407		"Accept-Encoding":   "deflate", // TODO: support lz4
408	}
409
410	r, err := request(p, "PUT", "/machinegroups/"+m.Name, h, body)
411	if err != nil {
412		return
413	}
414
415	body, err = ioutil.ReadAll(r.Body)
416	if err != nil {
417		return
418	}
419
420	if r.StatusCode != http.StatusOK {
421		errMsg := &errorMessage{}
422		err = json.Unmarshal(body, errMsg)
423		if err != nil {
424			err = fmt.Errorf("failed to update machine group")
425			dump, _ := httputil.DumpResponse(r, true)
426			fmt.Printf("%s\n", dump)
427			return
428		}
429		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
430		return
431	}
432
433	return
434}
435
436// DeleteMachineGroup deletes machine group according machine group name.
437func (p *LogProject) DeleteMachineGroup(name string) (err error) {
438	h := map[string]string{
439		"x-sls-bodyrawsize": "0",
440	}
441
442	r, err := request(p, "DELETE", "/machinegroups/"+name, h, nil)
443	if err != nil {
444		return
445	}
446
447	body, err := ioutil.ReadAll(r.Body)
448	if err != nil {
449		return
450	}
451
452	if r.StatusCode != http.StatusOK {
453		errMsg := &errorMessage{}
454		err = json.Unmarshal(body, errMsg)
455		if err != nil {
456			err = fmt.Errorf("failed to delete machine group")
457			dump, _ := httputil.DumpResponse(r, true)
458			fmt.Printf("%s\n", dump)
459			return
460		}
461		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
462		return
463	}
464	return
465}
466
467// ListConfig returns config names list and the total number of configs.
468// The offset starts from 0 and the size is the max number of configs could be returned.
469func (p *LogProject) ListConfig(offset, size int) (cfgNames []string, total int, err error) {
470	h := map[string]string{
471		"x-sls-bodyrawsize": "0",
472	}
473
474	if size <= 0 {
475		size = 100
476	}
477
478	uri := fmt.Sprintf("/configs?offset=%v&size=%v", offset, size)
479	r, err := request(p, "GET", uri, h, nil)
480	if err != nil {
481		return
482	}
483
484	buf, err := ioutil.ReadAll(r.Body)
485	if err != nil {
486		return
487	}
488
489	if r.StatusCode != http.StatusOK {
490		errMsg := &errorMessage{}
491		err = json.Unmarshal(buf, errMsg)
492		if err != nil {
493			err = fmt.Errorf("failed to delete machine group")
494			dump, _ := httputil.DumpResponse(r, true)
495			fmt.Printf("%s\n", dump)
496			return
497		}
498		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
499		return
500	}
501
502	type Body struct {
503		Total   int
504		Configs []string
505	}
506	body := &Body{}
507
508	err = json.Unmarshal(buf, body)
509	if err != nil {
510		return
511	}
512
513	cfgNames = body.Configs
514	total = body.Total
515	return
516}
517
518// GetConfig returns config according by config name.
519func (p *LogProject) GetConfig(name string) (c *LogConfig, err error) {
520	h := map[string]string{
521		"x-sls-bodyrawsize": "0",
522	}
523
524	r, err := request(p, "GET", "/configs/"+name, h, nil)
525	if err != nil {
526		return
527	}
528
529	buf, err := ioutil.ReadAll(r.Body)
530	if err != nil {
531		return
532	}
533
534	if r.StatusCode != http.StatusOK {
535		errMsg := &errorMessage{}
536		err = json.Unmarshal(buf, errMsg)
537		if err != nil {
538			err = fmt.Errorf("failed to delete config")
539			dump, _ := httputil.DumpResponse(r, true)
540			fmt.Printf("%s\n", dump)
541			return
542		}
543		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
544		return
545	}
546
547	c = &LogConfig{}
548	err = json.Unmarshal(buf, c)
549	if err != nil {
550		return
551	}
552	c.project = p
553	return
554}
555
556// UpdateConfig updates a config.
557func (p *LogProject) UpdateConfig(c *LogConfig) (err error) {
558
559	body, err := json.Marshal(c)
560	if err != nil {
561		return
562	}
563
564	h := map[string]string{
565		"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
566		"Content-Type":      "application/json",
567		"Accept-Encoding":   "deflate", // TODO: support lz4
568	}
569
570	r, err := request(p, "PUT", "/configs/"+c.Name, h, body)
571	if err != nil {
572		return
573	}
574
575	body, err = ioutil.ReadAll(r.Body)
576	if err != nil {
577		return
578	}
579
580	if r.StatusCode != http.StatusOK {
581		errMsg := &errorMessage{}
582		err = json.Unmarshal(body, errMsg)
583		if err != nil {
584			err = fmt.Errorf("failed to update config")
585			dump, _ := httputil.DumpResponse(r, true)
586			fmt.Printf("%s\n", dump)
587			return
588		}
589		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
590		return
591	}
592
593	return
594}
595
596// CreateConfig creates a new config in SLS.
597func (p *LogProject) CreateConfig(c *LogConfig) (err error) {
598
599	body, err := json.Marshal(c)
600	if err != nil {
601		return
602	}
603
604	h := map[string]string{
605		"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
606		"Content-Type":      "application/json",
607		"Accept-Encoding":   "deflate", // TODO: support lz4
608	}
609
610	r, err := request(p, "POST", "/configs", h, body)
611	if err != nil {
612		return
613	}
614
615	body, err = ioutil.ReadAll(r.Body)
616	if err != nil {
617		return
618	}
619
620	if r.StatusCode != http.StatusOK {
621		errMsg := &errorMessage{}
622		err = json.Unmarshal(body, errMsg)
623		if err != nil {
624			err = fmt.Errorf("failed to update config")
625			dump, _ := httputil.DumpResponse(r, true)
626			fmt.Printf("%s\n", dump)
627			return
628		}
629		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
630		return
631	}
632
633	return
634}
635
636// DeleteConfig deletes a config according by config name.
637func (p *LogProject) DeleteConfig(name string) (err error) {
638	h := map[string]string{
639		"x-sls-bodyrawsize": "0",
640	}
641
642	r, err := request(p, "DELETE", "/configs/"+name, h, nil)
643	if err != nil {
644		return
645	}
646
647	body, err := ioutil.ReadAll(r.Body)
648	if err != nil {
649		return
650	}
651
652	if r.StatusCode != http.StatusOK {
653		errMsg := &errorMessage{}
654		err = json.Unmarshal(body, errMsg)
655		if err != nil {
656			err = fmt.Errorf("failed to delete config")
657			dump, _ := httputil.DumpResponse(r, true)
658			fmt.Printf("%s\n", dump)
659			return
660		}
661		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
662		return
663	}
664	return
665}
666
667// GetAppliedMachineGroups returns applied machine group names list according config name.
668func (p *LogProject) GetAppliedMachineGroups(confName string) (groupNames []string, err error) {
669	h := map[string]string{
670		"x-sls-bodyrawsize": "0",
671	}
672
673	uri := fmt.Sprintf("/configs/%v/machinegroups", confName)
674	r, err := request(p, "GET", uri, h, nil)
675	if err != nil {
676		return
677	}
678
679	buf, err := ioutil.ReadAll(r.Body)
680	if err != nil {
681		return
682	}
683
684	if r.StatusCode != http.StatusOK {
685		errMsg := &errorMessage{}
686		err = json.Unmarshal(buf, errMsg)
687		if err != nil {
688			err = fmt.Errorf("failed to get applied machine groups")
689			dump, _ := httputil.DumpResponse(r, true)
690			fmt.Printf("%s\n", dump)
691			return
692		}
693		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
694		return
695	}
696
697	type Body struct {
698		Count         int
699		Machinegroups []string
700	}
701
702	body := &Body{}
703	err = json.Unmarshal(buf, body)
704	if err != nil {
705		return
706	}
707
708	groupNames = body.Machinegroups
709	return
710}
711
712// GetAppliedConfigs returns applied config names list according machine group name groupName.
713func (p *LogProject) GetAppliedConfigs(groupName string) (confNames []string, err error) {
714	h := map[string]string{
715		"x-sls-bodyrawsize": "0",
716	}
717
718	uri := fmt.Sprintf("/machinegroups/%v/configs", groupName)
719	r, err := request(p, "GET", uri, h, nil)
720	if err != nil {
721		return
722	}
723
724	buf, err := ioutil.ReadAll(r.Body)
725	if err != nil {
726		return
727	}
728
729	if r.StatusCode != http.StatusOK {
730		errMsg := &errorMessage{}
731		err = json.Unmarshal(buf, errMsg)
732		if err != nil {
733			err = fmt.Errorf("failed to applied configs")
734			dump, _ := httputil.DumpResponse(r, true)
735			fmt.Printf("%s\n", dump)
736			return
737		}
738		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
739		return
740	}
741
742	type Cfg struct {
743		Count   int      `json:"count"`
744		Configs []string `json:"configs"`
745	}
746
747	body := &Cfg{}
748	err = json.Unmarshal(buf, body)
749	if err != nil {
750		return
751	}
752
753	confNames = body.Configs
754	return
755}
756
757// ApplyConfigToMachineGroup applies config to machine group.
758func (p *LogProject) ApplyConfigToMachineGroup(confName, groupName string) (err error) {
759	h := map[string]string{
760		"x-sls-bodyrawsize": "0",
761	}
762
763	uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
764	r, err := request(p, "PUT", uri, h, nil)
765	if err != nil {
766		return
767	}
768
769	buf, err := ioutil.ReadAll(r.Body)
770	if err != nil {
771		return
772	}
773
774	if r.StatusCode != http.StatusOK {
775		errMsg := &errorMessage{}
776		err = json.Unmarshal(buf, errMsg)
777		if err != nil {
778			err = fmt.Errorf("failed to apply config to machine group")
779			dump, _ := httputil.DumpResponse(r, true)
780			fmt.Printf("%s\n", dump)
781			return
782		}
783		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
784		return
785	}
786	return
787}
788
789// RemoveConfigFromMachineGroup removes config from machine group.
790func (p *LogProject) RemoveConfigFromMachineGroup(confName, groupName string) (err error) {
791	h := map[string]string{
792		"x-sls-bodyrawsize": "0",
793	}
794
795	uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
796	r, err := request(p, "DELETE", uri, h, nil)
797	if err != nil {
798		return
799	}
800
801	buf, err := ioutil.ReadAll(r.Body)
802	if err != nil {
803		return
804	}
805
806	if r.StatusCode != http.StatusOK {
807		errMsg := &errorMessage{}
808		err = json.Unmarshal(buf, errMsg)
809		if err != nil {
810			err = fmt.Errorf("failed to remove config from machine group")
811			dump, _ := httputil.DumpResponse(r, true)
812			fmt.Printf("%s\n", dump)
813			return
814		}
815		err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
816		return
817	}
818	return
819}
820