/
gitlab.go
138 lines (117 loc) · 3.45 KB
/
gitlab.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package state
import (
"bytes"
"fmt"
"net/url"
"regexp"
"strconv"
"github.com/camptocamp/terraboard/config"
"github.com/camptocamp/terraboard/pkg/client/gitlab"
"github.com/hashicorp/terraform/states/statefile"
)
// Gitlab is a state provider type, leveraging GitLab
type Gitlab struct {
Client gitlab.Client
}
// NewGitlab creates a new Gitlab object
func NewGitlab(gl config.GitlabConfig) *Gitlab {
var instance *Gitlab
if gl.Token != "" {
instance = &Gitlab{
Client: gitlab.NewClient(gl.Address, gl.Token),
}
}
return instance
}
// NewGitlabCollection instantiate all needed Gitlab objects configurated by the user and return a slice
func NewGitlabCollection(c *config.Config) []*Gitlab {
var gitlabInstances []*Gitlab
for _, gitlab := range c.Gitlab {
gitlabInstances = append(gitlabInstances, NewGitlab(gitlab))
}
return gitlabInstances
}
// GetLocks returns a map of locks by State path
func (g *Gitlab) GetLocks() (locks map[string]LockInfo, err error) {
locks = make(map[string]LockInfo)
var projects gitlab.Projects
projects, err = g.Client.GetProjectsWithTerraformStates()
if err != nil {
return
}
for _, project := range projects {
for _, state := range project.TerraformStates {
if state.Lock != nil {
locks[state.GlobalPath()] = LockInfo{
ID: "N/A",
Operation: "N/A",
Info: "N/A",
Who: state.Lock.CreatedBy,
Version: "N/A",
Created: &state.Lock.CreatedAt,
Path: state.GlobalPath(),
}
}
}
}
return
}
// GetStates returns a slice of all found workspaces
func (g *Gitlab) GetStates() (states []string, err error) {
var projects gitlab.Projects
projects, err = g.Client.GetProjectsWithTerraformStates()
if err != nil {
return
}
for _, project := range projects {
for _, state := range project.TerraformStates {
states = append(states, state.GlobalPath())
}
}
return
}
// GetVersions returns a slice of Version objects
func (g *Gitlab) GetVersions(state string) (versions []Version, err error) {
var projects gitlab.Projects
projects, err = g.Client.GetProjectsWithTerraformStates()
if err != nil {
return
}
// TODO: Highly unoptimized: whether implement a GraphQL query to fetch the correct project only
// TODO: or cache the values locally
for _, project := range projects {
for _, s := range project.TerraformStates {
if state != s.GlobalPath() {
continue
}
for i := s.LatestVersion.Serial; i >= 0; i-- {
versions = append(versions, Version{
ID: strconv.Itoa(i),
// TODO: Fix/implement once https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45851 will be released
// somehow it seems to be working correctly though, not sure from which place it manages to find the correct date
LastModified: s.LatestVersion.CreatedAt,
})
}
}
}
return
}
// GetState retrieves a single state file from the GitLab API
func (g *Gitlab) GetState(path, version string) (sf *statefile.File, err error) {
re := regexp.MustCompile(`^\[(.*)] (.*)$`)
stateInfo := re.FindStringSubmatch(path)
if len(stateInfo) != 3 {
return nil, fmt.Errorf("invalid state path: %s", path)
}
var state []byte
state, err = g.Client.GetState(url.PathEscape(stateInfo[1]), url.PathEscape(stateInfo[2]), version)
if err != nil {
return
}
// Parse the statefile
sf, err = statefile.Read(bytes.NewReader(state))
if sf == nil {
return nil, fmt.Errorf("Unable to parse the statefile for workspace %s version %s", path, version)
}
return
}