Skip to content

Commit fe1b6c1

Browse files
author
Daniele Marostica
committed
added support to miactl project describe for legacy projects
it is now possible to specify ´--tag´ or `--branch` flags to retrieve the configuration for a legacy project
1 parent 7cff3e7 commit fe1b6c1

File tree

8 files changed

+239
-134
lines changed

8 files changed

+239
-134
lines changed

internal/clioptions/clioptions.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type CLIOptions struct {
4444

4545
Revision string
4646
Version string
47+
Branch string
48+
Tag string
4749
DeployType string
4850
NoSemVer bool
4951
TriggerID string
@@ -143,6 +145,14 @@ func (o *CLIOptions) AddVersionFlags(flags *pflag.FlagSet) {
143145
flags.StringVar(&o.Version, "version", "", "the version name of the configuration")
144146
}
145147

148+
func (o *CLIOptions) AddBranchFlags(flags *pflag.FlagSet) {
149+
flags.StringVar(&o.Branch, "branch", "", "the branch name of the configuration")
150+
}
151+
152+
func (o *CLIOptions) AddTagFlags(flags *pflag.FlagSet) {
153+
flags.StringVar(&o.Tag, "tag", "", "the tag name of the configuration")
154+
}
155+
146156
func (o *CLIOptions) AddEnvironmentFlags(flags *pflag.FlagSet) {
147157
flags.StringVar(&o.Environment, "environment", "", "the environment scope for the command")
148158
}

internal/cmd/project/apply.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func validateApplyProjectOptions(options applyProjectOptions) error {
116116
}
117117

118118
func applyConfiguration(ctx context.Context, client *client.APIClient, options applyProjectOptions) error {
119-
ref, err := configuration.GetEncodedRevisionRef(options.RevisionName)
119+
ref, err := configuration.NewRef(configuration.RevisionRefType, options.RevisionName)
120120
if err != nil {
121121
return err
122122
}
@@ -142,7 +142,7 @@ func applyConfiguration(ctx context.Context, client *client.APIClient, options a
142142
return fmt.Errorf("cannot encode project configuration: %w", err)
143143
}
144144

145-
endpoint := fmt.Sprintf("/api/backend/projects/%s/%s/configuration", options.ProjectID, ref)
145+
endpoint := fmt.Sprintf("/api/backend/projects/%s/%s/configuration", options.ProjectID, ref.EncodedLocationPath())
146146
response, err := client.
147147
Post().
148148
APIPath(endpoint).

internal/cmd/project/describe.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@ const (
3131
describeProjectCmdUsage = "describe"
3232
describeProjectCmdShort = "Describe a Project configuration"
3333
describeProjectCmdLong = `Describe the configuration of the specified Project.`
34+
35+
ErrMultipleIdentifiers = "multiple identifiers specified, please provide only one"
3436
)
3537

3638
type describeProjectOptions struct {
37-
ProjectID string
3839
RevisionName string
3940
VersionName string
41+
BranchName string
42+
TagName string
43+
ProjectID string
4044
OutputFormat string
4145
}
4246

@@ -56,6 +60,8 @@ func DescribeCmd(options *clioptions.CLIOptions) *cobra.Command {
5660
cmdOptions := describeProjectOptions{
5761
RevisionName: options.Revision,
5862
VersionName: options.Version,
63+
BranchName: options.Branch,
64+
TagName: options.Tag,
5965
ProjectID: restConfig.ProjectID,
6066
OutputFormat: options.OutputFormat,
6167
}
@@ -68,6 +74,8 @@ func DescribeCmd(options *clioptions.CLIOptions) *cobra.Command {
6874
options.AddProjectFlags(flags)
6975
options.AddRevisionFlags(flags)
7076
options.AddVersionFlags(flags)
77+
options.AddBranchFlags(flags)
78+
options.AddTagFlags(flags)
7179
options.AddOutputFormatFlag(flags, "json")
7280

7381
return cmd
@@ -78,12 +86,12 @@ func describeProject(ctx context.Context, client *client.APIClient, options desc
7886
return fmt.Errorf("missing project name, please provide a project name as argument")
7987
}
8088

81-
ref, err := configuration.GetEncodedRef(options.RevisionName, options.VersionName)
89+
ref, err := GetRefFromOptions(options)
8290
if err != nil {
8391
return err
8492
}
8593

86-
endpoint := fmt.Sprintf("/api/backend/projects/%s/%s/configuration", options.ProjectID, ref)
94+
endpoint := fmt.Sprintf("/api/backend/projects/%s/%s/configuration/", options.ProjectID, ref.EncodedLocationPath())
8795
response, err := client.
8896
Get().
8997
APIPath(endpoint).
@@ -113,3 +121,46 @@ func describeProject(ctx context.Context, client *client.APIClient, options desc
113121
fmt.Fprintln(writer, string(bytes))
114122
return nil
115123
}
124+
125+
func GetRefFromOptions(options describeProjectOptions) (configuration.Ref, error) {
126+
refType := ""
127+
refName := ""
128+
129+
if len(options.RevisionName) > 0 {
130+
refType = configuration.RevisionRefType
131+
refName = options.RevisionName
132+
}
133+
134+
if len(options.VersionName) > 0 {
135+
if len(refType) > 0 {
136+
return configuration.Ref{}, fmt.Errorf(ErrMultipleIdentifiers)
137+
}
138+
139+
refType = configuration.VersionRefType
140+
refName = options.VersionName
141+
}
142+
143+
if len(options.BranchName) > 0 {
144+
if len(refType) > 0 {
145+
return configuration.Ref{}, fmt.Errorf(ErrMultipleIdentifiers)
146+
}
147+
148+
refType = configuration.BranchRefType
149+
refName = options.BranchName
150+
}
151+
152+
if len(options.TagName) > 0 {
153+
if len(refType) > 0 {
154+
return configuration.Ref{}, fmt.Errorf(ErrMultipleIdentifiers)
155+
}
156+
157+
refType = configuration.TagRefType
158+
refName = options.TagName
159+
}
160+
161+
if len(refType) == 0 {
162+
return configuration.Ref{}, fmt.Errorf("missing revision/version/branch/tag name, please provide one as argument")
163+
}
164+
165+
return configuration.NewRef(refType, refName)
166+
}

internal/cmd/project/describe_test.go

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,57 @@ func TestDescribeProjectCmd(t *testing.T) {
5858
return false
5959
}),
6060
},
61-
"error missing revision/version": {
61+
"error missing revision/version/branch/tag": {
6262
options: describeProjectOptions{
6363
ProjectID: "test-project",
6464
},
6565
expectError: true,
66-
expectedErrorMsg: "missing revision/version name, please provide one as argument",
66+
expectedErrorMsg: "missing revision/version/branch/tag name, please provide one as argument",
6767
testServer: describeTestServer(t, func(_ http.ResponseWriter, _ *http.Request) bool {
6868
return false
6969
}),
7070
},
71-
"error both revision/version specified": {
71+
"error multiple revision/version specified": {
7272
options: describeProjectOptions{
7373
ProjectID: "test-project",
7474
RevisionName: "test-revision",
7575
VersionName: "test-version",
7676
},
7777
expectError: true,
78-
expectedErrorMsg: "both revision and version specified, please provide only one",
78+
expectedErrorMsg: "multiple identifiers specified, please provide only one",
7979
testServer: describeTestServer(t, func(_ http.ResponseWriter, _ *http.Request) bool {
8080
return false
8181
}),
8282
},
83+
"error multiple branch/revision specified": {
84+
options: describeProjectOptions{
85+
ProjectID: "test-project",
86+
RevisionName: "test-revision",
87+
BranchName: "test-branch",
88+
},
89+
expectError: true,
90+
expectedErrorMsg: "multiple identifiers specified, please provide only one",
91+
testServer: describeTestServer(t, func(_ http.ResponseWriter, _ *http.Request) bool {
92+
return false
93+
}),
94+
},
95+
"valid project with branch": {
96+
options: describeProjectOptions{
97+
ProjectID: "test-project",
98+
BranchName: "test-json-branch",
99+
OutputFormat: "json",
100+
},
101+
revisionName: "test-revision",
102+
testServer: describeTestServer(t, func(w http.ResponseWriter, r *http.Request) bool {
103+
if r.URL.Path == "/api/backend/projects/test-project/branches/test-json-branch/configuration/" && r.Method == http.MethodGet {
104+
w.WriteHeader(http.StatusOK)
105+
_, _ = w.Write([]byte(`{"name": "test-project", "branch": "test-json-branch"}`))
106+
return true
107+
}
108+
return false
109+
}),
110+
outputTextJSON: `{"config": {"name": "test-project", "branch": "test-json-branch"}}`,
111+
},
83112
"valid project with revision": {
84113
options: describeProjectOptions{
85114
ProjectID: "test-project",
@@ -88,7 +117,7 @@ func TestDescribeProjectCmd(t *testing.T) {
88117
},
89118
revisionName: "test-revision",
90119
testServer: describeTestServer(t, func(w http.ResponseWriter, r *http.Request) bool {
91-
if r.URL.Path == "/api/backend/projects/test-project/revisions/test-json-revision/configuration" && r.Method == http.MethodGet {
120+
if r.URL.Path == "/api/backend/projects/test-project/revisions/test-json-revision/configuration/" && r.Method == http.MethodGet {
92121
w.WriteHeader(http.StatusOK)
93122
_, _ = w.Write([]byte(`{"name": "test-project", "revision": "test-json-revision"}`))
94123
return true
@@ -97,14 +126,30 @@ func TestDescribeProjectCmd(t *testing.T) {
97126
}),
98127
outputTextJSON: `{"config": {"name": "test-project", "revision": "test-json-revision"}}`,
99128
},
129+
"valid project with tag": {
130+
options: describeProjectOptions{
131+
ProjectID: "test-project",
132+
TagName: "test-tag",
133+
OutputFormat: "json",
134+
},
135+
testServer: describeTestServer(t, func(w http.ResponseWriter, r *http.Request) bool {
136+
if r.URL.Path == "/api/backend/projects/test-project/branches/test-tag/configuration/" && r.Method == http.MethodGet {
137+
w.WriteHeader(http.StatusOK)
138+
_, _ = w.Write([]byte(`{"name": "test-project", "tag": "test-tag"}`))
139+
return true
140+
}
141+
return false
142+
}),
143+
outputTextJSON: `{"config": {"name": "test-project", "tag": "test-tag"}}`,
144+
},
100145
"valid project with version": {
101146
options: describeProjectOptions{
102147
ProjectID: "test-project",
103148
VersionName: "test-version",
104149
OutputFormat: "json",
105150
},
106151
testServer: describeTestServer(t, func(w http.ResponseWriter, r *http.Request) bool {
107-
if r.URL.Path == "/api/backend/projects/test-project/versions/test-version/configuration" && r.Method == http.MethodGet {
152+
if r.URL.Path == "/api/backend/projects/test-project/versions/test-version/configuration/" && r.Method == http.MethodGet {
108153
w.WriteHeader(http.StatusOK)
109154
_, _ = w.Write([]byte(`{"name": "test-project", "revision": "test-version"}`))
110155
return true
@@ -120,7 +165,7 @@ func TestDescribeProjectCmd(t *testing.T) {
120165
OutputFormat: "yaml",
121166
},
122167
testServer: describeTestServer(t, func(w http.ResponseWriter, r *http.Request) bool {
123-
if r.URL.Path == "/api/backend/projects/test-project/revisions/test-yaml-revision/configuration" && r.Method == http.MethodGet {
168+
if r.URL.Path == "/api/backend/projects/test-project/revisions/test-yaml-revision/configuration/" && r.Method == http.MethodGet {
124169
w.WriteHeader(http.StatusOK)
125170
_, _ = w.Write([]byte(`{"name": "test-project", "revision": "test-yaml-revision"}`))
126171
return true
@@ -136,7 +181,7 @@ func TestDescribeProjectCmd(t *testing.T) {
136181
OutputFormat: "yaml",
137182
},
138183
testServer: describeTestServer(t, func(w http.ResponseWriter, r *http.Request) bool {
139-
if r.URL.Path == "/api/backend/projects/test-project/revisions/some%2Frevision/configuration" && r.Method == http.MethodGet {
184+
if r.URL.Path == "/api/backend/projects/test-project/revisions/some%2Frevision/configuration/" && r.Method == http.MethodGet {
140185
w.WriteHeader(http.StatusOK)
141186
_, _ = w.Write([]byte(`{"name": "test-project", "revision": "test-yaml-revision"}`))
142187
return true
@@ -152,7 +197,7 @@ func TestDescribeProjectCmd(t *testing.T) {
152197
OutputFormat: "yaml",
153198
},
154199
testServer: describeTestServer(t, func(w http.ResponseWriter, r *http.Request) bool {
155-
if r.URL.Path == "/api/backend/projects/test-project/versions/version%2F1.2.3/configuration" && r.Method == http.MethodGet {
200+
if r.URL.Path == "/api/backend/projects/test-project/versions/version%2F1.2.3/configuration/" && r.Method == http.MethodGet {
156201
w.WriteHeader(http.StatusOK)
157202
_, _ = w.Write([]byte(`{"name": "test-project", "revision": "test-yaml-revision"}`))
158203
return true
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package configuration
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
)
7+
8+
type RefTypes map[string]bool
9+
10+
const (
11+
RevisionRefType = "revisions"
12+
VersionRefType = "versions"
13+
BranchRefType = "branches"
14+
TagRefType = "tags"
15+
)
16+
17+
var validRefTypes = RefTypes{RevisionRefType: true, VersionRefType: true, BranchRefType: true, TagRefType: true}
18+
19+
type Ref struct {
20+
refType string
21+
refName string
22+
}
23+
24+
func NewRef(refType, refName string) (Ref, error) {
25+
if !validRefTypes[refType] {
26+
return Ref{}, fmt.Errorf("unknown reference type: %s", refType)
27+
}
28+
if len(refName) == 0 {
29+
return Ref{}, fmt.Errorf("missing reference name, please provide a reference name")
30+
}
31+
return Ref{
32+
refType: refType,
33+
refName: refName,
34+
}, nil
35+
}
36+
37+
// EncodedLocationPath returns the encoded path to be used when fetching configuration data
38+
//
39+
// e.g., "<ConsoleURL>/api/projects/<ProjectID>/<EncodedLocationPath()>/configuration"
40+
func (r Ref) EncodedLocationPath() string {
41+
switch r.refType {
42+
case RevisionRefType, VersionRefType:
43+
return fmt.Sprintf("%s/%s", r.refType, url.PathEscape(r.refName))
44+
case BranchRefType, TagRefType:
45+
// Legacy projects use /branches endpoint only
46+
return fmt.Sprintf("branches/%s", url.PathEscape(r.refName))
47+
default:
48+
return ""
49+
}
50+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package configuration
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestNewRef(t *testing.T) {
11+
tests := []struct {
12+
refType string
13+
refName string
14+
expectError bool
15+
expectedErrorMsg string
16+
}{
17+
{"revisions", "rev1", false, ""},
18+
{"versions", "v1", false, ""},
19+
{"branches", "branch1", false, ""},
20+
{"tags", "tag1", false, ""},
21+
{"revisions", "with/slash", false, ""},
22+
{"versions", "with/slash", false, ""},
23+
{"branches", "with/slash", false, ""},
24+
{"tags", "with/slash", false, ""},
25+
26+
{"invalidType", "name", true, "unknown reference type: invalidType"},
27+
}
28+
29+
for _, tt := range tests {
30+
t.Run(fmt.Sprintf("%s/%s", tt.refType, tt.refName), func(t *testing.T) {
31+
ref, err := NewRef(tt.refType, tt.refName)
32+
if tt.expectError {
33+
require.EqualError(t, err, tt.expectedErrorMsg)
34+
return
35+
}
36+
require.NoError(t, err)
37+
require.Equal(t, tt.refType, ref.refType)
38+
require.Equal(t, tt.refName, ref.refName)
39+
})
40+
}
41+
}
42+
43+
func TestGetEncodedResourceLocation(t *testing.T) {
44+
tests := []struct {
45+
refType string
46+
refName string
47+
expected string
48+
expectError bool
49+
expectedErrorMsg string
50+
}{
51+
{"revisions", "rev1", "revisions/rev1", false, ""},
52+
{"versions", "v1", "versions/v1", false, ""},
53+
{"branches", "branch1", "branches/branch1", false, ""},
54+
{"tags", "tag1", "branches/tag1", false, ""},
55+
{"revisions", "with/slash", "revisions/with%2Fslash", false, ""},
56+
{"versions", "with/slash", "versions/with%2Fslash", false, ""},
57+
{"branches", "with/slash", "branches/with%2Fslash", false, ""},
58+
}
59+
60+
for _, tt := range tests {
61+
t.Run(tt.expected, func(t *testing.T) {
62+
ref, err := NewRef(tt.refType, tt.refName)
63+
require.NoError(t, err)
64+
65+
encodedString := ref.EncodedLocationPath()
66+
require.Equal(t, encodedString, tt.expected)
67+
})
68+
}
69+
}

0 commit comments

Comments
 (0)