Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

medic: print policies as tables #880

Merged
merged 1 commit into from
Sep 7, 2023
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
6 changes: 3 additions & 3 deletions cmd/cli/app/policy/policy_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ within a mediator control plane.`,
return fmt.Errorf("error creating policy: %w", err)
}

out, err := util.GetJsonFromProto(resp)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
table := initializeTable(cmd)
renderPolicyTable(resp.GetPolicy(), table)
table.Render()
return nil
},
}
Expand Down
92 changes: 32 additions & 60 deletions cmd/cli/app/policy/policy_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ package policy
import (
"fmt"
"os"
"time"

"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"github.com/spf13/viper"

Expand All @@ -43,7 +41,7 @@ mediator control plane.`,
provider := viper.GetString("provider")
format := viper.GetString("output")

if format != app.JSON && format != app.YAML && format != "" {
if format != app.JSON && format != app.YAML && format != app.Table {
return fmt.Errorf("error: invalid format: %s", format)
}

Expand All @@ -56,61 +54,28 @@ mediator control plane.`,
defer cancel()

id := viper.GetInt32("id")
status := util.GetConfigValue("status", "status", cmd, false).(bool)
if status {
resp, err := client.GetPolicyStatusById(ctx, &pb.GetPolicyStatusByIdRequest{
Context: &pb.Context{
Provider: provider,
// TODO set up group if specified
// Currently it's inferred from the authorization token
},
PolicyId: id,
})
util.ExitNicelyOnError(err, "Error getting policy status")

// print results
if format == "" {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Policy ID", "Policy Name", "Status", "Last updated"})

st := resp.GetPolicyStatus()
row := []string{
fmt.Sprintf("%d", st.PolicyId),
st.PolicyName,
st.PolicyStatus,
st.GetLastUpdated().AsTime().Format(time.RFC3339),
}
table.Append(row)
table.Render()
} else if format == app.JSON {
out, err := util.GetJsonFromProto(resp)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
} else if format == app.YAML {
out, err := util.GetYamlFromProto(resp)
util.ExitNicelyOnError(err, "Error getting yaml from proto")
fmt.Println(out)
}
} else {
policy, err := client.GetPolicyById(ctx, &pb.GetPolicyByIdRequest{
Context: &pb.Context{
Provider: provider,
// TODO set up group if specified
// Currently it's inferred from the authorization token
},
Id: id,
})
util.ExitNicelyOnError(err, "Error getting policy")

if format == app.YAML {
out, err := util.GetYamlFromProto(policy)
util.ExitNicelyOnError(err, "Error getting yaml from proto")
fmt.Println(out)
} else {
out, err := util.GetJsonFromProto(policy)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
}
policy, err := client.GetPolicyById(ctx, &pb.GetPolicyByIdRequest{
Context: &pb.Context{
Provider: provider,
// TODO set up group if specified
// Currently it's inferred from the authorization token
},
Id: id,
})
util.ExitNicelyOnError(err, "Error getting policy")

switch format {
case app.YAML:
out, err := util.GetYamlFromProto(policy)
util.ExitNicelyOnError(err, "Error getting yaml from proto")
fmt.Println(out)
case app.JSON:
out, err := util.GetJsonFromProto(policy)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
case app.Table:
p := policy.GetPolicy()
handleGetTableOutput(cmd, p)
}

return nil
Expand All @@ -120,8 +85,7 @@ mediator control plane.`,
func init() {
PolicyCmd.AddCommand(policy_getCmd)
policy_getCmd.Flags().Int32P("id", "i", 0, "ID for the policy to query")
policy_getCmd.Flags().BoolP("status", "s", false, "Only return the status of the policy for all the associated repos")
policy_getCmd.Flags().StringP("output", "o", "", "Output format (json or yaml)")
policy_getCmd.Flags().StringP("output", "o", app.Table, "Output format (json, yaml or table)")
policy_getCmd.Flags().StringP("provider", "p", "github", "Provider for the policy")
// TODO set up group if specified

Expand All @@ -131,3 +95,11 @@ func init() {
}

}

func handleGetTableOutput(cmd *cobra.Command, policy *pb.PipelinePolicy) {
table := initializeTable(cmd)

renderPolicyTable(policy, table)

table.Render()
}
28 changes: 23 additions & 5 deletions cmd/cli/app/policy/policy_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/stacklok/mediator/cmd/cli/app"
"github.com/stacklok/mediator/internal/util"
pb "github.com/stacklok/mediator/pkg/generated/protobuf/go/mediator/v1"
)
Expand Down Expand Up @@ -51,8 +52,12 @@ mediator control plane for an specific group.`,

provider := viper.GetString("provider")

if format != "json" && format != "yaml" {
fmt.Fprintf(os.Stderr, "Error: invalid format: %s\n", format)
switch format {
case app.JSON:
case app.YAML:
case app.Table:
default:
return fmt.Errorf("invalid format: %s", format)
}

resp, err := client.ListPolicies(ctx, &pb.ListPoliciesRequest{
Expand All @@ -66,14 +71,18 @@ mediator control plane for an specific group.`,
return fmt.Errorf("error getting policies: %w", err)
}

if format == "json" {
switch format {
case app.JSON:
out, err := util.GetJsonFromProto(resp)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
} else if format == "yaml" {
case app.YAML:
out, err := util.GetYamlFromProto(resp)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
case app.Table:
handleListTableOutput(cmd, resp)
return nil
}

// this is unreachable
Expand All @@ -84,7 +93,7 @@ mediator control plane for an specific group.`,
func init() {
PolicyCmd.AddCommand(policy_listCmd)
policy_listCmd.Flags().StringP("provider", "p", "", "Provider to list policies for")
policy_listCmd.Flags().StringP("output", "o", "yaml", "Output format (json or yaml)")
policy_listCmd.Flags().StringP("output", "o", app.Table, "Output format (json, yaml or table)")
// TODO: Take group ID into account
// policy_listCmd.Flags().Int32P("group-id", "g", 0, "group id to list roles for")

Expand All @@ -93,3 +102,12 @@ func init() {
os.Exit(1)
}
}

func handleListTableOutput(cmd *cobra.Command, resp *pb.ListPoliciesResponse) {
table := initializeTable(cmd)

for _, v := range resp.Policies {
renderPolicyTable(v, table)
}
table.Render()
}
125 changes: 125 additions & 0 deletions cmd/cli/app/policy/table_render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// Copyright 2023 Stacklok, Inc.
//
// 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 policy

import (
"fmt"

"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"google.golang.org/protobuf/types/known/structpb"
"gopkg.in/yaml.v2"

"github.com/stacklok/mediator/pkg/entities"
pb "github.com/stacklok/mediator/pkg/generated/protobuf/go/mediator/v1"
)

func initializeTable(cmd *cobra.Command) *tablewriter.Table {
table := tablewriter.NewWriter(cmd.OutOrStdout())
table.SetHeader([]string{"Id", "Name", "Provider", "Entity", "Context", "Rule", "Rule Params", "Rule Definition"})
table.SetRowLine(true)
table.SetRowSeparator("-")
table.SetAutoMergeCellsByColumnIndex([]int{0, 1, 2, 3, 4, 5})
// This is needed for the rule definition and rule parameters
table.SetAutoWrapText(false)

return table
}

func renderPolicyTable(
p *pb.PipelinePolicy,
table *tablewriter.Table,
) {
// repositories
renderEntityRuleSets(p, entities.RepositoryEntity, p.Repository, table)

// build_environments
renderEntityRuleSets(p, entities.BuildEnvironmentEntity, p.BuildEnvironment, table)

// artifacts
renderEntityRuleSets(p, entities.ArtifactEntity, p.Artifact, table)
}

func renderEntityRuleSets(
p *pb.PipelinePolicy,
entType entities.EntityType,
rs []*pb.PipelinePolicy_ContextualRuleSet,
table *tablewriter.Table,
) {
for idx := range rs {
rs := rs[idx]

renderContextualRuleSetTable(p, entType, rs, table)
}
}

func renderContextualRuleSetTable(
p *pb.PipelinePolicy,
entType entities.EntityType,
ctxrs *pb.PipelinePolicy_ContextualRuleSet,
table *tablewriter.Table,
) {
for idx := range ctxrs.Rules {
rule := ctxrs.Rules[idx]

ruleCtx := ""
if ctxrs.Context != nil {
ruleCtx = *ctxrs.Context
}

renderRuleTable(p, entType, ruleCtx, rule, table)
}
}

func renderRuleTable(
p *pb.PipelinePolicy,
entType entities.EntityType,
ruleContext string,
rule *pb.PipelinePolicy_Rule,
table *tablewriter.Table,
) {

params := marshalStructOrEmpty(rule.Params)
def := marshalStructOrEmpty(rule.Def)

row := []string{
fmt.Sprintf("%d", *p.Id),
p.Name,
p.Context.Provider,
entType.String(),
ruleContext,
rule.Type,
params,
def,
}
table.Append(row)
}

func marshalStructOrEmpty(v *structpb.Struct) string {
if v == nil {
return ""
}

m := v.AsMap()

// marhsal as YAML
out, err := yaml.Marshal(m)
if err != nil {
return ""
}

return string(out)
}
3 changes: 3 additions & 0 deletions cmd/cli/app/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const JSON = "json"
// YAML is the yaml format for output
const YAML = "yaml"

// Table is the table format for output
const Table = "table"

// Execute adds all child commands to the root command and sets flags appropriately.
func Execute() {
err := RootCmd.Execute()
Expand Down
18 changes: 9 additions & 9 deletions pkg/entities/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,34 @@ import (
)

// EntityType is the type of entity
type entityType string
type EntityType string

// Entity types as string-like enums. Used in CLI and other user-facing code
const (
// RepositoryEntity is a repository entity
RepositoryEntity entityType = "repository"
RepositoryEntity EntityType = "repository"
// BuildEnvironmentEntity is a build environment entity
BuildEnvironmentEntity entityType = "build_environment"
BuildEnvironmentEntity EntityType = "build_environment"
// ArtifactEntity is an artifact entity
ArtifactEntity entityType = "artifact"
ArtifactEntity EntityType = "artifact"
// UnknownEntity is an explicitly unknown entity
UnknownEntity entityType = "unknown"
UnknownEntity EntityType = "unknown"
)

// String returns the string representation of the entity type
func (e entityType) String() string {
func (e EntityType) String() string {
return string(e)
}

// Enum value maps for Entity.
var (
entityTypeToPb = map[entityType]pb.Entity{
entityTypeToPb = map[EntityType]pb.Entity{
RepositoryEntity: pb.Entity_ENTITY_REPOSITORIES,
BuildEnvironmentEntity: pb.Entity_ENTITY_BUILD_ENVIRONMENTS,
ArtifactEntity: pb.Entity_ENTITY_ARTIFACTS,
UnknownEntity: pb.Entity_ENTITY_UNSPECIFIED,
}
pbToEntityType = map[pb.Entity]entityType{
pbToEntityType = map[pb.Entity]EntityType{
pb.Entity_ENTITY_REPOSITORIES: RepositoryEntity,
pb.Entity_ENTITY_BUILD_ENVIRONMENTS: BuildEnvironmentEntity,
pb.Entity_ENTITY_ARTIFACTS: ArtifactEntity,
Expand All @@ -72,7 +72,7 @@ func IsValidEntity(entity pb.Entity) bool {
// FromString returns the Entity enum from a string. Typically used in CLI
// when constructing a protobuf message
func FromString(entity string) pb.Entity {
et := entityType(strings.ToLower(entity))
et := EntityType(strings.ToLower(entity))
// take advantage of the default value of the map being pb.Entity_ENTITY_UNSPECIFIED
return entityTypeToPb[et]
}
Expand Down