Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `miactl deploy latest` for getting the latest successful deployment for a project in a specified environment
- `miactl runtime create job` has two additional new flags:
- `--waitJobCompletion` (default `false`) - if enabled, the `miactl` will wait for the job completion
- `--waitJobTimeoutSeconds` (default `600` seconds) - if `--waitJobCompletion` is enabled, the maximum wait timeout
Expand Down
4 changes: 4 additions & 0 deletions internal/clioptions/clioptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ func (o *CLIOptions) AddDeployAddStatusFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.TriggerID, "trigger-id", "", "trigger-id of the pipeline to update")
}

func (o *CLIOptions) AddDeployLatestFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.Environment, "environment", "", "the environment scope for the command")
}

func (o *CLIOptions) AddContextAuthFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.BasicClientID, "client-id", "", "the client ID of the service account")
flags.StringVar(&o.BasicClientSecret, "client-secret", "", "the client secret of the service account")
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Can trigger deployments to specific environments and monitor their status.`,
cmd.AddCommand(
triggerCmd(options),
newStatusAddCmd(options),
latestDeploymentCmd(options),
)

return cmd
Expand Down
101 changes: 101 additions & 0 deletions internal/cmd/deploy/latest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright Mia srl
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package deploy

import (
"context"
"errors"
"fmt"

"github.com/spf13/cobra"

"github.com/mia-platform/miactl/internal/client"
"github.com/mia-platform/miactl/internal/clioptions"
"github.com/mia-platform/miactl/internal/resources"
)

const (
deploymentsLatestEndpointTemplate = "/api/deploy/projects/%s/deployment/"
)

func latestDeploymentCmd(options *clioptions.CLIOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "latest",
Short: "Get the latest deployment for the project",
Long: "Get the latest deployment for the project in the specified environment.",
RunE: func(cmd *cobra.Command, args []string) error {
return runLatestDeployment(cmd.Context(), options)
},
}

flags := cmd.Flags()
options.AddDeployLatestFlags(flags)

return cmd
}

func runLatestDeployment(ctx context.Context, options *clioptions.CLIOptions) error {
restConfig, err := options.ToRESTConfig()
if err != nil {
return err
}

projectID := restConfig.ProjectID
if len(projectID) == 0 {
return errors.New("projectId is required")
}

client, err := client.APIClientForConfig(restConfig)
if err != nil {
return err
}

resp, err := client.
Get().
APIPath(fmt.Sprintf(deploymentsLatestEndpointTemplate, projectID)).
SetParam("page", "1").
SetParam("per_page", "1").
SetParam("scope", "success").
SetParam("environment", options.Environment).
Do(ctx)

if err != nil {
return fmt.Errorf("error executing request: %w", err)
}

if err := resp.Error(); err != nil {
return err
}

var deployments []resources.DeploymentHistory
if err := resp.ParseResponse(&deployments); err != nil {
return fmt.Errorf("cannot parse server response: %w", err)
}

if len(deployments) == 0 {
fmt.Println("No successful deployments found")
return nil
}

latest := deployments[0]
fmt.Printf("Latest deployment for environment %s:\n", latest.Environment)
fmt.Printf("ID: %s\n", latest.ID)
fmt.Printf("Ref: %s\n", latest.Ref)
fmt.Printf("Status: %s\n", latest.Status)
fmt.Printf("Finished At: %s\n", latest.FinishedAt)

return nil
}
87 changes: 87 additions & 0 deletions internal/cmd/deploy/latest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright Mia srl
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package deploy

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/mia-platform/miactl/internal/clioptions"
"github.com/mia-platform/miactl/internal/resources"
)

func TestLatestDeployment(t *testing.T) {
testProjectID := "test-project-id"
testEnv := "dev"
now := time.Now().Truncate(time.Second)

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, fmt.Sprintf("/api/deploy/projects/%s/deployment/", testProjectID), r.URL.Path)
assert.Equal(t, "1", r.URL.Query().Get("page"))
assert.Equal(t, "1", r.URL.Query().Get("per_page"))
assert.Equal(t, "success", r.URL.Query().Get("scope"))
assert.Equal(t, testEnv, r.URL.Query().Get("environment"))

resp := []resources.DeploymentHistory{
{
ID: "deploy-123",
Ref: "main",
PipelineID: "pipe-123",
Status: "success",
FinishedAt: now,
Environment: testEnv,
},
}
data, _ := resources.EncodeResourceToJSON(resp)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
}))
defer server.Close()

options := clioptions.NewCLIOptions()
options.Endpoint = server.URL
options.ProjectID = testProjectID
options.Environment = testEnv

err := runLatestDeployment(t.Context(), options)
assert.NoError(t, err)
}

func TestLatestDeploymentNoResults(t *testing.T) {
testProjectID := "test-project-id"
testEnv := "dev"

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("[]"))
}))
defer server.Close()

options := clioptions.NewCLIOptions()
options.Endpoint = server.URL
options.ProjectID = testProjectID
options.Environment = testEnv

err := runLatestDeployment(t.Context(), options)
assert.NoError(t, err)
}
15 changes: 15 additions & 0 deletions internal/resources/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,21 @@ type Deployment struct {
Age time.Time `json:"creationTimestamp"` //nolint: tagliatelle
}

type DeploymentHistory struct {
ID string `json:"id"`
Ref string `json:"ref"`
PipelineID string `json:"pipelineId"`
Status string `json:"status"`
FinishedAt time.Time `json:"finishedAt"`
Environment string `json:"env"` //nolint:tagliatelle
EnvironmentInfo EnvironmentInfo `json:"environmentInfo"`
}

type EnvironmentInfo struct {
EnvID string `json:"envId"`
Label string `json:"label"`
}

type Service struct {
Name string `json:"name"`
Type string `json:"type"`
Expand Down