Skip to content

Commit 337ee89

Browse files
authored
Merge pull request #6 from rschmied/dev
Named configs and version 2.7 support
2 parents 5368653 + 8547e89 commit 337ee89

27 files changed

+1251
-711
lines changed

.github/workflows/go.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ jobs:
1111
build:
1212
runs-on: ubuntu-latest
1313
steps:
14-
- uses: actions/checkout@v3
14+
- uses: actions/checkout@v4
1515

1616
- name: Set up Go
17-
uses: actions/setup-go@v3
17+
uses: actions/setup-go@v5
1818
with:
19-
go-version: 1.18
19+
go-version: 1.21
2020

2121
- name: Build
2222
run: go build -v ./...

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
coverage.out
22
*.code-workspace
3+
.envrc
4+
.tool-versions
35

CHANGELOG.md

+17-25
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
Lists the changes in the gocmlclient package.
44

5+
## Version 0.1.0
6+
7+
- making a somewhat bigger version bump due to some bigger changes
8+
- moved logging to log/slog
9+
- removed all the caching logic / code. It didn't really work well due to races. In addition, TF doesn't really keep a connection / client over multiple resource calls so the caching was somewhat limited even it would have properly worked (which it did not).
10+
- named configurations (added with CML 2.7)
11+
- added some tests for the named configs
12+
513
## Version 0.0.23
614

715
- added LinkDestroy() method
@@ -34,9 +42,7 @@ fix header and connection error
3442
## Version 0.0.17
3543

3644
- added cache control headers to requests
37-
- return ErrSystemNotReady for Connection refused and 502, also always
38-
reset the client's compatibility error property when versionCheck is called
39-
so that it always queries the backend.
45+
- return ErrSystemNotReady for Connection refused and 502, also always reset the client's compatibility error property when versionCheck is called so that it always queries the backend.
4046
- bumped semver to 3.2.1
4147

4248
## Version 0.0.16
@@ -49,43 +55,32 @@ fix header and connection error
4955

5056
## Version 0.0.15
5157

52-
- made node configuration a pointer to differentiate between
53-
"no configuration" (null), "empty configuration" and "specific
54-
configuration". With a null configuration, the default configuration
55-
from the node definition will be inserted if there is one
58+
- made node configuration a pointer to differentiate between "no configuration" (null), "empty configuration" and "specific configuration". With a null configuration, the default configuration from the node definition will be inserted if there is one
5659
- added Version var/func, moved NewClient() to New()
5760
- bump go to 1.19 and vendor deps
5861

5962
## Version 0.0.12
6063

61-
- Realized that the empty tags removal from 0.0.11 caused a regression.
62-
node tags are always returned/set even when there's no tags... in that
63-
case, the empty list is returned or needs to be provided. See 0.0.3 comment.
64+
- Realized that the empty tags removal from 0.0.11 caused a regression. node tags are always returned/set even when there's no tags... in that case, the empty list is returned or needs to be provided. See 0.0.3 comment.
6465
- Test coverage improvement
6566

6667
## Version 0.0.8 to 0.0.11
6768

6869
- Added most of the doc string for exported functions.
6970
- reversed the sorting of images for the image definitions.
70-
- sort image definitions by their ID. Lists have the newest (highest version)
71-
image as the first element.
71+
- sort image definitions by their ID. Lists have the newest (highest version) image as the first element.
7272
- updated dependencies.
73-
- have InterfaceCreate accept a slot value (not a pointer). A negative slot
74-
indicates "don't specify a slot", this was previously indicated by nil.
73+
- have InterfaceCreate accept a slot value (not a pointer). A negative slot indicates "don't specify a slot", this was previously indicated by nil.
7574
- added more values to the ImageDefinition and Nodedefinition structs.
7675
- added a link unit test.
7776
- more node attributes can be updated when a node is DEFINED_ON_CORE
78-
- NodeCreate removes a node now when the 2nd API call fails. The 2nd call is
79-
needed to update certain attributes which are not accepted in the actual
80-
create API (POST).
77+
- NodeCreate removes a node now when the 2nd API call fails. The 2nd call is needed to update certain attributes which are not accepted in the actual create API (POST).
8178
- move the upper version for the version constraint from <2.6.0 to <3.0.0.
8279
- omit empty tags on update.
8380

8481
## Version 0.0.5 to 0.0.7
8582

86-
- refactored the code so that interfaces are read in one go ("data=true"). This
87-
without this, only a list of interface IDs is returned by the API. With this,
88-
the API returns a list of complete interface object.
83+
- refactored the code so that interfaces are read in one go ("data=true"). This without this, only a list of interface IDs is returned by the API. With this, the API returns a list of complete interface object.
8984
- Implement the same approach for nodes (0.0.6).
9085
- updated dependencies.
9186
- Due to the data=true option, restrict the code to only work with 2.4.0 and later.
@@ -97,12 +92,9 @@ fix header and connection error
9792

9893
## Version 0.0.3
9994

100-
- Fixed node tag list update. To delete all tags from a node, an empty tag list
101-
must be serialized in the `PATCH` JSON. This was prevented by having
102-
`omitempty` in the struct. Fixed
95+
- Fixed node tag list update. To delete all tags from a node, an empty tag list must be serialized in the `PATCH` JSON. This was prevented by having `omitempty` in the struct. Fixed
10396
- Also moved the `ctest` cmd fro the terraform provider repo to the code base.
10497

10598
## Versions prior to 0.0.3
10699

107-
Nothing in particular to be noteworthy -- just huge chunks of initial code
108-
refactoring.
100+
Nothing in particular to be noteworthy -- just huge chunks of initial code refactoring.

apiclient.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"io"
9-
"log"
9+
"log/slog"
1010
"net/http"
1111
"net/url"
1212
"strings"
@@ -64,7 +64,7 @@ func (c *Client) doAPI(ctx context.Context, req *http.Request, depth int32) ([]b
6464
}
6565

6666
if c.state.get() != stateAuthenticated && c.authRequired(req.URL) {
67-
log.Println("needs auth")
67+
slog.Info("needs auth")
6868
c.state.set(stateAuthenticating)
6969
if err := c.jsonGet(ctx, authokAPI, nil, depth); err != nil {
7070
return nil, err
@@ -102,7 +102,7 @@ retry:
102102
if res.StatusCode == http.StatusUnauthorized {
103103
invalid_token := len(c.apiToken) > 0
104104
c.apiToken = ""
105-
log.Println("need to authenticate")
105+
slog.Info("need to authenticate")
106106
c.state.set(stateAuthRequired)
107107
if !c.userpass.valid() {
108108
errmsg := "no credentials provided"

apiclient_test.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@ import (
88
"github.com/stretchr/testify/assert"
99
)
1010

11-
const useCache bool = false
12-
1311
type testClient struct {
1412
client *Client
1513
mr *mr.MockResponder
1614
ctx context.Context
1715
}
1816

1917
func newTestAPIclient() testClient {
20-
c := New("https://controller", true, useCache)
18+
c := New("https://controller", true)
2119
mrClient, ctx := mr.NewMockResponder()
2220
c.httpClient = mrClient
2321
c.SetUsernamePassword("user", "pass")
@@ -31,7 +29,7 @@ func newAuthedTestAPIclient() testClient {
3129
}
3230

3331
func TestClient_methoderror(t *testing.T) {
34-
c := New("", true, useCache)
32+
c := New("", true)
3533
err := c.jsonReq(context.Background(), "ü", "###", nil, nil, 0)
3634
assert.Error(t, err)
3735
}

auth.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"crypto/x509"
77
"encoding/json"
88
"errors"
9-
"log"
9+
"log/slog"
1010
"net/http"
1111
"net/url"
1212
"strconv"
@@ -36,8 +36,8 @@ func (up userPass) valid() bool {
3636
return len(up.Username) > 0 && len(up.Password) > 0
3737
}
3838

39-
// technically, authokAPI requires auth, but it's used specifically
40-
// to test whether auth is OK, so it will take a different path
39+
// technically, authokAPI requires auth, but it's used specifically to test
40+
// whether auth is OK, so it will take a different path
4141
func (c *Client) authRequired(api *url.URL) bool {
4242
url := api.String()
4343
return !(strings.HasSuffix(url, authAPI) ||
@@ -56,16 +56,16 @@ func (c *Client) authenticate(ctx context.Context, userpass userPass, depth int3
5656
if err != nil {
5757
return err
5858
}
59-
log.Printf("user id %s, is admin: %s", auth.ID, strconv.FormatBool(auth.Admin))
59+
slog.Info("user auth", "id", auth.ID, "is_admin", strconv.FormatBool(auth.Admin))
6060
c.apiToken = auth.Token
6161
return nil
6262
}
6363

6464
// SetToken sets a specific API token to be used. A token takes precedence over
65-
// a username/password. However, if the token expires, the username/password are
66-
// used to authorize the client again. An error is raised if no token and no
67-
// username/password are provided or if the token expires when no username/password
68-
// are set.
65+
// a username/password. However, if the token expires, the username/password
66+
// are used to authorize the client again. An error is raised if no token and
67+
// no username/password are provided or if the token expires when no
68+
// username/password are set.
6969
func (c *Client) SetToken(token string) {
7070
c.apiToken = token
7171
}

auth_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,20 @@ func TestClient_token_auth(t *testing.T) {
151151
}
152152

153153
func TestClient_SetToken(t *testing.T) {
154-
c := New("https://bla.bla", true, useCache)
154+
c := New("https://bla.bla", true)
155155
c.SetToken("qwe")
156156
assert.Equal(t, "qwe", c.apiToken)
157157
}
158158

159159
func TestClient_SetUsernamePassword(t *testing.T) {
160-
c := New("https://bla.bla", true, useCache)
160+
c := New("https://bla.bla", true)
161161
c.SetUsernamePassword("user", "pass")
162162
assert.Equal(t, "user", c.userpass.Username)
163163
assert.Equal(t, "pass", c.userpass.Password)
164164
}
165165

166166
func TestClient_SetCACert(t *testing.T) {
167-
c := New("https://bla.bla", true, useCache)
167+
c := New("https://bla.bla", true)
168168
err := c.SetCACert([]byte("crapdata"))
169169
assert.EqualError(t, err, "failed to parse root certificate")
170170
testCA := "testdata/ca.pem"

cmd/ctest/main.go

+58-12
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,36 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
6-
"log"
7+
"fmt"
8+
"log/slog"
79
"os"
10+
"time"
811

12+
"github.com/lmittmann/tint"
913
cmlclient "github.com/rschmied/gocmlclient"
1014
)
1115

1216
func main() {
17+
// set global logger with custom options
18+
slog.SetDefault(slog.New(
19+
tint.NewHandler(os.Stderr, &tint.Options{
20+
AddSource: true,
21+
Level: slog.LevelDebug,
22+
TimeFormat: time.RFC822,
23+
}),
24+
))
25+
1326
// address and lab id
1427
host, found := os.LookupEnv("CML_HOST")
1528
if !found {
16-
log.Println("CML_HOST env var not found!")
29+
slog.Error("CML_HOST env var not found!")
1730
return
1831
}
1932
// labID, found := os.LookupEnv("CML_LABID")
2033
// if !found {
21-
// log.Println("CML_LABID env var not found!")
34+
// slog.Error("CML_LABID env var not found!")
2235
// return
2336
// }
2437
// _ = labID
@@ -28,11 +41,11 @@ func main() {
2841
password, pass_found := os.LookupEnv("CML_PASSWORD")
2942
token, token_found := os.LookupEnv("CML_TOKEN")
3043
if !(token_found || (user_found && pass_found)) {
31-
log.Println("either CML_TOKEN or CML_USERNAME and CML_PASSWORD env vars must be present!")
44+
slog.Error("either CML_TOKEN or CML_USERNAME and CML_PASSWORD env vars must be present!")
3245
return
3346
}
3447
ctx := context.Background()
35-
client := cmlclient.New(host, true, false)
48+
client := cmlclient.New(host, false)
3649
// if err := client.Ready(ctx); err != nil {
3750
// log.Fatal(err)
3851
// }
@@ -86,16 +99,49 @@ func main() {
8699
// result, err := client.UserGroups(ctx, "cc42bd56-1dc6-445c-b7e7-569b0a8b0c94")
87100
err := client.Ready(ctx)
88101
if errors.Is(err, cmlclient.ErrSystemNotReady) {
89-
log.Println("it is not ready")
102+
slog.Error("it is not ready")
103+
return
90104
}
91105
if err != nil && !errors.Is(err, cmlclient.ErrSystemNotReady) {
92-
log.Println(err)
106+
slog.Error("ready", slog.Any("error", err))
107+
return
108+
}
109+
/* node := &cmlclient.Node{
110+
// ID: "28ec08ec-483a-415a-a3ed-625b0d45bef0",
111+
// ID: "8116a609-8b68-4e0f-a196-5225da9f05c0",
112+
ID: "0577f1c4-4907-4c49-a4fd-c6daa61b6e78",
113+
LabID: "2b7435f2-b247-4cc8-8509-6b0d0f593c4c",
114+
}
115+
node, err = client.NodeGet(ctx, node, false)
116+
if err != nil {
117+
slog.Error("nodeget", slog.Any("error", err))
93118
return
94119
}
95120
96-
// je, err := json.Marshal(result)
97-
// if err != nil {
98-
// log.Println(err)
99-
// }
100-
// fmt.Println(string(je))
121+
je, err := json.Marshal(node)
122+
if err != nil {
123+
slog.Error("marshal", slog.Any("error", err))
124+
return
125+
}
126+
fmt.Println(string(je)) */
127+
128+
lab, err := client.LabGet(ctx, "2b7435f2-b247-4cc8-8509-6b0d0f593c4c", true)
129+
if err != nil {
130+
slog.Error("get", slog.Any("error", err))
131+
return
132+
}
133+
134+
for _, v := range lab.Nodes {
135+
if v.Configuration != nil {
136+
fmt.Printf("[1] %T: %s\n", v.Configuration, *v.Configuration)
137+
}
138+
fmt.Printf("[2] %T: %+v\n", v.Configurations, v.Configurations)
139+
}
140+
return
141+
je, err := json.Marshal(lab)
142+
if err != nil {
143+
slog.Error("marshal", slog.Any("error", err))
144+
return
145+
}
146+
fmt.Println(string(je))
101147
}

cml.go

+5-7
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,13 @@ type Client struct {
1919
compatibilityErr error
2020
state *apiClientState
2121
mu sync.RWMutex
22-
labCache map[string]*Lab
23-
useCache bool
22+
useNamedConfigs bool
2423
version string
2524
}
2625

27-
// New returns a new CML client instance. The host must be a valid URL including
28-
// scheme (https://).
29-
func New(host string, insecure, useCache bool) *Client {
26+
// New returns a new CML client instance. The host must be a valid URL
27+
// including scheme (https://).
28+
func New(host string, insecure bool) *Client {
3029
tr := http.DefaultTransport.(*http.Transport)
3130
tr.TLSClientConfig = &tls.Config{
3231
InsecureSkipVerify: insecure,
@@ -44,7 +43,6 @@ func New(host string, insecure, useCache bool) *Client {
4443
},
4544
compatibilityErr: nil,
4645
state: newState(),
47-
labCache: make(map[string]*Lab),
48-
useCache: useCache,
46+
useNamedConfigs: false,
4947
}
5048
}

error.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmlclient
33
import "errors"
44

55
var (
6-
ErrSystemNotReady = errors.New("system not ready")
7-
ErrElementNotFound = errors.New("element not found")
6+
ErrSystemNotReady = errors.New("system not ready")
7+
ErrElementNotFound = errors.New("element not found")
8+
ErrNoNamedConfigSupport = errors.New("backend does not support named configs")
89
)

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
module github.com/rschmied/gocmlclient
22

3-
go 1.20
3+
go 1.21
44

55
require (
66
github.com/Masterminds/semver/v3 v3.2.1
7+
github.com/lmittmann/tint v1.0.4
78
github.com/rschmied/mockresponder v1.0.4
89
github.com/stretchr/testify v1.8.2
910
golang.org/x/sync v0.7.0

0 commit comments

Comments
 (0)