Skip to content

Commit 70fe7e6

Browse files
feat: project apply command (#257)
1 parent 9e09358 commit 70fe7e6

17 files changed

+1763
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- added `project describe` command
13+
- added `project apply` command
1314

1415
## [v0.19.0] - 2025-06-18
1516

internal/clioptions/clioptions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ type CLIOptions struct {
8989
ShowServiceAccounts bool
9090

9191
ResolveExtensionsDetails bool
92+
93+
Message string
9294
}
9395

9496
// NewCLIOptions return a new CLIOptions instance

internal/cmd/project.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ resources that make up the applications of a specific company.
4444
project.IAMCmd(o),
4545
project.ImportCmd(o),
4646
project.DescribeCmd(o),
47+
project.ApplyCmd(o),
4748
)
4849

4950
return projectCmd

internal/cmd/project/apply.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright Mia srl
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package project
17+
18+
import (
19+
"context"
20+
"fmt"
21+
22+
"github.com/mia-platform/miactl/internal/client"
23+
"github.com/mia-platform/miactl/internal/clioptions"
24+
"github.com/mia-platform/miactl/internal/files"
25+
"github.com/mia-platform/miactl/internal/resources"
26+
"github.com/mia-platform/miactl/internal/resources/configuration"
27+
28+
"github.com/spf13/cobra"
29+
)
30+
31+
const (
32+
applyProjectCmdUsage = "apply"
33+
applyProjectCmdShort = "Apply a Project configuration"
34+
applyProjectCmdLong = `Apply a Project configuration from a file.
35+
36+
The configuration file should contain a complete project configuration in JSON or YAML format.
37+
This command will replace the current project configuration with the one provided in the file.
38+
`
39+
)
40+
41+
type applyProjectOptions struct {
42+
ProjectID string
43+
RevisionName string
44+
FilePath string
45+
Title string
46+
}
47+
48+
// ApplyCmd returns a cobra command for applying a project configuration
49+
func ApplyCmd(options *clioptions.CLIOptions) *cobra.Command {
50+
cmd := &cobra.Command{
51+
Use: applyProjectCmdUsage,
52+
Short: applyProjectCmdShort,
53+
Long: applyProjectCmdLong,
54+
RunE: func(cmd *cobra.Command, _ []string) error {
55+
restConfig, err := options.ToRESTConfig()
56+
cobra.CheckErr(err)
57+
58+
client, err := client.APIClientForConfig(restConfig)
59+
cobra.CheckErr(err)
60+
61+
cmdOptions := applyProjectOptions{
62+
RevisionName: options.Revision,
63+
ProjectID: restConfig.ProjectID,
64+
FilePath: options.InputFilePath,
65+
Title: options.Message,
66+
}
67+
68+
return handleApplyProjectConfigurationCmd(cmd.Context(), client, cmdOptions)
69+
},
70+
}
71+
72+
flags := cmd.Flags()
73+
options.AddProjectFlags(flags)
74+
options.AddRevisionFlags(flags)
75+
76+
flags.StringVarP(&options.Message, "message", "m", "", "the message to use when saving the configuration")
77+
78+
// file path flag is required
79+
flags.StringVarP(&options.InputFilePath, "file", "f", "", "path to JSON/YAML file containing the project configuration")
80+
if err := cmd.MarkFlagRequired("file"); err != nil {
81+
panic(err)
82+
}
83+
84+
return cmd
85+
}
86+
87+
func handleApplyProjectConfigurationCmd(ctx context.Context, client *client.APIClient, options applyProjectOptions) error {
88+
err := validateApplyProjectOptions(options)
89+
if err != nil {
90+
return err
91+
}
92+
93+
err = applyConfiguration(ctx, client, options)
94+
if err != nil {
95+
return fmt.Errorf("failed to apply project configuration: %w", err)
96+
}
97+
98+
fmt.Println("Project configuration applied successfully")
99+
return nil
100+
}
101+
102+
func validateApplyProjectOptions(options applyProjectOptions) error {
103+
if len(options.ProjectID) == 0 {
104+
return fmt.Errorf("missing project name, please provide a project name as argument")
105+
}
106+
107+
if len(options.FilePath) == 0 {
108+
return fmt.Errorf("missing file path, please provide a file path with the -f flag")
109+
}
110+
111+
if len(options.RevisionName) == 0 {
112+
return fmt.Errorf("missing revision name, please provide a revision name")
113+
}
114+
115+
return nil
116+
}
117+
118+
func applyConfiguration(ctx context.Context, client *client.APIClient, options applyProjectOptions) error {
119+
ref, err := configuration.GetEncodedRevisionRef(options.RevisionName)
120+
if err != nil {
121+
return err
122+
}
123+
124+
projectConfig := make(map[string]any)
125+
if err := files.ReadFile(options.FilePath, &projectConfig); err != nil {
126+
return fmt.Errorf("failed to read project configuration file: %w", err)
127+
}
128+
129+
structuredConfig, err := configuration.BuildDescribeConfiguration(projectConfig)
130+
if err != nil {
131+
return fmt.Errorf("cannot parse project configuration: %w", err)
132+
}
133+
134+
applyConfig := configuration.BuildApplyRequest(structuredConfig)
135+
136+
if options.Title != "" {
137+
applyConfig = applyConfig.WithTitle(options.Title)
138+
}
139+
140+
body, err := resources.EncodeResourceToJSON(applyConfig)
141+
if err != nil {
142+
return fmt.Errorf("cannot encode project configuration: %w", err)
143+
}
144+
145+
endpoint := fmt.Sprintf("/api/backend/projects/%s/%s/configuration", options.ProjectID, ref)
146+
response, err := client.
147+
Post().
148+
APIPath(endpoint).
149+
Body(body).
150+
Do(ctx)
151+
if err != nil {
152+
return fmt.Errorf("failed to apply project configuration: %w", err)
153+
}
154+
if err := response.Error(); err != nil {
155+
return err
156+
}
157+
158+
return nil
159+
}

0 commit comments

Comments
 (0)