Skip to content

Commit 23335a0

Browse files
feat: apply extension command (#205)
1 parent 5a8a5b7 commit 23335a0

25 files changed

+705
-45
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
- `extensions list` command
13+
- `extensions apply` command
1314
- `extensions delete` command
1415
- `extensions activate` command
1516
- `extensions deactivate` command

docs/30_commands.md

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,175 @@ Available flags for the command:
556556
- `--no-semver`, to force the deploy without `semver`
557557
- `--revision`, to specify the revision of the commit to deploy
558558

559+
## extensions
560+
561+
The `extensions` command allows you to manage Company extensions.
562+
563+
Available subcommands are the following ones:
564+
565+
```sh
566+
list List registered extensions
567+
apply Create or update an extension
568+
activate Activate an extension
569+
deactivate Deactivate an extension
570+
delete Delete extension
571+
```
572+
573+
### list
574+
575+
The `extensions list` command helps you gathering available extension in your Company
576+
577+
Usage:
578+
579+
```sh
580+
miactl extensions list [flags]
581+
```
582+
583+
Available flags for the command:
584+
585+
- `--company-id` to set the ID of the desired Company
586+
587+
### apply
588+
589+
The `extensions apply` command can be used to register new extensions or update an existing one.
590+
591+
It accepts an Extension Manifest either in `yaml` or `json` format
592+
593+
<details>
594+
<summary>Example JSON Manifest<summary>
595+
596+
```json
597+
{
598+
"name": "Extension 1",
599+
"description": "My extension 1",
600+
"entry": "https://example.com/",
601+
"contexts": [
602+
"project"
603+
],
604+
"routes": [
605+
{
606+
"id": "extension-1",
607+
"parentId": "workloads",
608+
"locationId": "runtime",
609+
"renderType": "menu",
610+
"labelIntl": {
611+
"en": "SomeLabel",
612+
"it": "SomeLabelInItalian"
613+
},
614+
"destinationPath": "/",
615+
"order": 200.0,
616+
"icon": {
617+
"name": "PiHardDrives"
618+
}
619+
}
620+
]
621+
}
622+
```
623+
624+
</details>
625+
626+
<details>
627+
<summary>Example YAML Manifest<summary>
628+
629+
```yaml
630+
name: "Extension 1"
631+
description: "My extension 1"
632+
entry: "https://example.com/"
633+
contexts:
634+
- project
635+
routes:
636+
- id: "extension-1"
637+
parentId: "workloads"
638+
locationId: "runtime"
639+
labelIntl:
640+
en: "SomeLabel"
641+
it: "SomeLabelInItalian"
642+
destinationPath: "/"
643+
renderType: "menu"
644+
order: 200
645+
icon:
646+
name: "PiHardDrives"
647+
```
648+
649+
</details>
650+
651+
Usage:
652+
653+
```sh
654+
miactl extensions apply [flags]
655+
```
656+
657+
Available flags for the command:
658+
659+
- `--company-id` to set the ID of the desired Company
660+
- `--file-path` (`-f`) **required** to specify the path to the extension manifest
661+
- `--extension-id` to set the ID of the extension Company, required for updating an existing extension.
662+
663+
:::tip
664+
In order to specify whether a create or an update is needed you have to use the `--extension-id` 
665+
flag or specify the `extensionId` property in the manifest file.
666+
667+
You can get the **extension id** by using the [extensions list](#list-5) command or
668+
in the apply response after creating the extension.
669+
:::
670+
671+
### activate
672+
673+
The `extensions activate` command can be used to delete an existing extension.
674+
675+
:::tip
676+
You can activate an extension on the whole Company or only for specific Projects.
677+
If you activate it for the whole Company it will be inherited by all the Projects
678+
:::
679+
680+
Usage:
681+
682+
```sh
683+
miactl extensions activate [flags]
684+
```
685+
686+
Available flags for the command:
687+
688+
- `--company-id` to set the ID of the desired Company
689+
- `--project-id` to set the ID of the desired project, if specified, the extension will be activated only for this project only
690+
- `--extension-id` **required** to set the ID of the extension.
691+
692+
### deactivate
693+
694+
The `extensions deactivate` command can be used to delete an existing extension.
695+
696+
:::tip
697+
Please note that if an extension has been activated on the whole Company it can't be deactivated on a specific Project;
698+
you have to deactivate on the whole Company and activate it on the desired Projects.
699+
:::
700+
701+
Usage:
702+
703+
```sh
704+
miactl extensions deactivate [flags]
705+
```
706+
707+
Available flags for the command:
708+
709+
- `--company-id` to set the ID of the desired Company
710+
- `--project-id` to set the ID of the desired project, if specified, the extension will be deactivated only for this project only
711+
- `--extension-id` **required** to set the ID of the extension.
712+
713+
### delete
714+
715+
The `extensions delete` command can be used to delete an existing extension.
716+
717+
Usage:
718+
719+
```sh
720+
miactl extensions delete [flags]
721+
```
722+
723+
Available flags for the command:
724+
725+
- `--company-id` to set the ID of the desired Company
726+
- `--extension-id` **required** to set the ID of the extension, required for updating an existing extension.
727+
559728
## runtime
560729

561730
### environment list
@@ -691,7 +860,7 @@ View and manage Marketplace items
691860

692861
All the subcommands inherit the following flags:
693862

694-
```
863+
```sh
695864
--auth-name string the name of the miactl auth to use
696865
--certificate-authority string path to a cert file for the certificate authority for the selected endpoint
697866
--company-id string the ID of the Company

internal/client/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ func (c *APIClient) Post() *Request {
7474
return NewRequest(c).SetVerb(http.MethodPost)
7575
}
7676

77+
// Put return a new Request object for a Put http request
78+
func (c *APIClient) Put() *Request {
79+
return NewRequest(c).SetVerb(http.MethodPut)
80+
}
81+
7782
// Delete return a new Request object for a DELETE http request
7883
func (c *APIClient) Delete() *Request {
7984
return NewRequest(c).SetVerb(http.MethodDelete)

internal/client/request.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ func (r *Request) preflightCheck() error {
151151
}
152152

153153
switch {
154+
case r.verb == http.MethodPut && len(r.body) == 0:
155+
fallthrough
154156
case r.verb == http.MethodPost && len(r.body) == 0:
155157
return fmt.Errorf("empty body for a POST request")
156158
case r.verb == http.MethodGet && len(r.body) > 0:

internal/client/request_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ func TestPreflightCheck(t *testing.T) {
125125
"correct POST": {
126126
request: (&Request{}).SetVerb("POST").Body([]byte("hello")),
127127
},
128+
"correct PUT": {
129+
request: (&Request{}).SetVerb("PUT").Body([]byte("hello")),
130+
},
128131
"empty verb": {
129132
request: &Request{},
130133
err: true,
@@ -137,6 +140,10 @@ func TestPreflightCheck(t *testing.T) {
137140
request: (&Request{}).SetVerb("POST").Body([]byte{}),
138141
err: true,
139142
},
143+
"empty body for PUT": {
144+
request: (&Request{}).SetVerb("PUT").Body([]byte{}),
145+
err: true,
146+
},
140147
"valid verb and body but preexisting error": {
141148
request: (&Request{err: fmt.Errorf("")}).SetVerb("GET"),
142149
err: true,

internal/clioptions/clioptions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ type CLIOptions struct {
6464
JWTJsonPath string
6565
OutputPath string
6666

67+
InputFilePath string
68+
6769
MarketplaceResourcePaths []string
6870
// MarketplaceItemID is the itemId field of a Marketplace item
6971
MarketplaceItemID string

internal/cmd/extensions/activate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ context to select where you want to activate it.`,
5656
},
5757
}
5858

59-
addExtensionIDRequiredFlag(o, cmd)
59+
addExtensionIDFlag(o, cmd)
6060
return cmd
6161
}

internal/cmd/extensions/apply.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 extensions
17+
18+
import (
19+
"fmt"
20+
21+
"github.com/mia-platform/miactl/internal/client"
22+
"github.com/mia-platform/miactl/internal/clioptions"
23+
"github.com/mia-platform/miactl/internal/files"
24+
"github.com/mia-platform/miactl/internal/resources/extensibility"
25+
26+
"github.com/spf13/cobra"
27+
)
28+
29+
func ApplyCmd(o *clioptions.CLIOptions) *cobra.Command {
30+
cmd := &cobra.Command{
31+
Use: "apply",
32+
Short: "Create or update an extension",
33+
Long: `Use this command to create a new extension or to update an existing one.
34+
35+
Extension data must be provided with a file, submitted with the -f flag; you can specificy the
36+
extension-id either within the manifest file or via command line argument.
37+
38+
If an extension-id is found an updated is performed, if not instead a new extension will be created.`,
39+
RunE: func(cmd *cobra.Command, _ []string) error {
40+
restConfig, err := o.ToRESTConfig()
41+
cobra.CheckErr(err)
42+
43+
client, err := client.APIClientForConfig(restConfig)
44+
cobra.CheckErr(err)
45+
46+
if restConfig.CompanyID == "" {
47+
return ErrRequiredCompanyID
48+
}
49+
50+
extensionData, err := readExtensionFromFile(o.InputFilePath)
51+
if err != nil {
52+
return err
53+
}
54+
55+
if o.EntityID != "" && extensionData.ExtensionID != "" && o.EntityID != extensionData.ExtensionID {
56+
return fmt.Errorf("extension id has been provided both with flags and manifest and they mismatch")
57+
}
58+
59+
if o.EntityID != "" {
60+
extensionData.ExtensionID = o.EntityID
61+
}
62+
63+
if extensionData.ExtensionType == "" {
64+
extensionData.ExtensionType = IFrameExtensionType
65+
}
66+
67+
extensibilityClient := New(client)
68+
extensionID, err := extensibilityClient.Apply(cmd.Context(), restConfig.CompanyID, extensionData)
69+
cobra.CheckErr(err)
70+
fmt.Printf("Successfully applied extension with id %s\n", extensionID)
71+
return nil
72+
},
73+
}
74+
75+
addExtensionIDFlag(o, cmd)
76+
requireFilePathFlag(o, cmd)
77+
78+
return cmd
79+
}
80+
81+
func readExtensionFromFile(path string) (*extensibility.Extension, error) {
82+
extensionData := &extensibility.Extension{}
83+
if err := files.ReadFile(path, extensionData); err != nil {
84+
return nil, err
85+
}
86+
87+
return extensionData, nil
88+
}

0 commit comments

Comments
 (0)