Add policy ls (#3753)
This commit is contained in:
parent
77bcba412e
commit
223e0c5e83
|
@ -11,6 +11,8 @@ CHANGELOG
|
|||
|
||||
- Add Permalink to output when publishing a Policy Pack.
|
||||
|
||||
- Add `pulumi policy ls` and `pulumi policy group ls` to list Policy related resources.
|
||||
|
||||
## 1.8.1 (2019-12-20)
|
||||
|
||||
- Fix a panic in `pulumi stack select`. [#3687](https://github.com/pulumi/pulumi/pull/3687)
|
||||
|
|
|
@ -28,6 +28,8 @@ func newPolicyCmd() *cobra.Command {
|
|||
|
||||
cmd.AddCommand(newPolicyDisableCmd())
|
||||
cmd.AddCommand(newPolicyEnableCmd())
|
||||
cmd.AddCommand(newPolicyGroupCmd())
|
||||
cmd.AddCommand(newPolicyLsCmd())
|
||||
cmd.AddCommand(newPolicyNewCmd())
|
||||
cmd.AddCommand(newPolicyPublishCmd())
|
||||
cmd.AddCommand(newPolicyRmCmd())
|
||||
|
|
137
cmd/policy_group_ls.go
Normal file
137
cmd/policy_group_ls.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation.
|
||||
//
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/apitype"
|
||||
"github.com/pulumi/pulumi/pkg/backend/display"
|
||||
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newPolicyGroupCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "group",
|
||||
Short: "Manage policy groups",
|
||||
Args: cmdutil.NoArgs,
|
||||
}
|
||||
|
||||
cmd.AddCommand(newPolicyGroupLsCmd())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newPolicyGroupLsCmd() *cobra.Command {
|
||||
var jsonOut bool
|
||||
var cmd = &cobra.Command{
|
||||
Use: "ls [org-name]",
|
||||
Args: cmdutil.MaximumNArgs(1),
|
||||
Short: "List all Policy Groups for a Pulumi organization",
|
||||
Long: "List all Policy Groups for a Pulumi organization",
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, cliArgs []string) error {
|
||||
// Get backend.
|
||||
b, err := currentBackend(display.Options{Color: cmdutil.GetGlobalColorization()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get organization.
|
||||
var orgName string
|
||||
if len(cliArgs) > 0 {
|
||||
orgName = cliArgs[0]
|
||||
} else {
|
||||
orgName, err = b.CurrentUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// List the Policy Packs for the organization.
|
||||
ctx := context.Background()
|
||||
policyGroups, err := b.ListPolicyGroups(ctx, orgName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOut {
|
||||
return formatPolicyGroupsJSON(policyGroups)
|
||||
}
|
||||
return formatPolicyGroupsConsole(policyGroups)
|
||||
}),
|
||||
}
|
||||
cmd.PersistentFlags().BoolVarP(
|
||||
&jsonOut, "json", "j", false, "Emit output as JSON")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func formatPolicyGroupsConsole(policyGroups apitype.ListPolicyGroupsResponse) error {
|
||||
// Header string and formatting options to align columns.
|
||||
headers := []string{"NAME", "DEFAULT", "ENABLED POLICY PACKS", "STACKS"}
|
||||
|
||||
rows := []cmdutil.TableRow{}
|
||||
|
||||
for _, group := range policyGroups.PolicyGroups {
|
||||
// Name column
|
||||
name := group.Name
|
||||
|
||||
// Default column
|
||||
var defaultGroup string
|
||||
if group.IsOrgDefault {
|
||||
defaultGroup = "Y"
|
||||
} else {
|
||||
defaultGroup = "N"
|
||||
}
|
||||
|
||||
// Number of enabled Policy Packs column
|
||||
numPolicyPacks := strconv.Itoa(group.NumEnabledPolicyPacks)
|
||||
|
||||
// Number of stacks colum
|
||||
numStacks := strconv.Itoa(group.NumStacks)
|
||||
|
||||
// Render the columns.
|
||||
columns := []string{name, defaultGroup, numPolicyPacks, numStacks}
|
||||
rows = append(rows, cmdutil.TableRow{Columns: columns})
|
||||
}
|
||||
cmdutil.PrintTable(cmdutil.Table{
|
||||
Headers: headers,
|
||||
Rows: rows,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// policyGroupsJSON is the shape of the --json output of this command. When --json is passed, we print an array
|
||||
// of policyGroupsJSON objects. While we can add fields to this structure in the future, we should not change
|
||||
// existing fields.
|
||||
type policyGroupsJSON struct {
|
||||
Name string `json:"name"`
|
||||
Default bool `json:"default"`
|
||||
NumPolicyPacks int `json:"numPolicyPacks"`
|
||||
NumStacks int `json:"numStacks"`
|
||||
}
|
||||
|
||||
func formatPolicyGroupsJSON(policyGroups apitype.ListPolicyGroupsResponse) error {
|
||||
output := make([]policyGroupsJSON, len(policyGroups.PolicyGroups))
|
||||
for i, group := range policyGroups.PolicyGroups {
|
||||
output[i] = policyGroupsJSON{
|
||||
Name: group.Name,
|
||||
Default: group.IsOrgDefault,
|
||||
NumPolicyPacks: group.NumEnabledPolicyPacks,
|
||||
NumStacks: group.NumStacks,
|
||||
}
|
||||
}
|
||||
return printJSON(output)
|
||||
}
|
113
cmd/policy_ls.go
Normal file
113
cmd/policy_ls.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation.
|
||||
//
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/apitype"
|
||||
"github.com/pulumi/pulumi/pkg/backend/display"
|
||||
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newPolicyLsCmd() *cobra.Command {
|
||||
var jsonOut bool
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "ls [org-name]",
|
||||
Args: cmdutil.MaximumNArgs(1),
|
||||
Short: "List all Policy Packs for a Pulumi organization",
|
||||
Long: "List all Policy Packs for a Pulumi organization",
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, cliArgs []string) error {
|
||||
// Get backend.
|
||||
b, err := currentBackend(display.Options{Color: cmdutil.GetGlobalColorization()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get organization.
|
||||
var orgName string
|
||||
if len(cliArgs) > 0 {
|
||||
orgName = cliArgs[0]
|
||||
} else {
|
||||
orgName, err = b.CurrentUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// List the Policy Packs for the organization.
|
||||
ctx := context.Background()
|
||||
policyPacks, err := b.ListPolicyPacks(ctx, orgName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOut {
|
||||
return formatPolicyPacksJSON(policyPacks)
|
||||
}
|
||||
return formatPolicyPacksConsole(policyPacks)
|
||||
}),
|
||||
}
|
||||
cmd.PersistentFlags().BoolVarP(
|
||||
&jsonOut, "json", "j", false, "Emit output as JSON")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func formatPolicyPacksConsole(policyPacks apitype.ListPolicyPacksResponse) error {
|
||||
// Header string and formatting options to align columns.
|
||||
headers := []string{"NAME", "VERSIONS"}
|
||||
|
||||
rows := []cmdutil.TableRow{}
|
||||
|
||||
for _, packs := range policyPacks.PolicyPacks {
|
||||
// Name column
|
||||
name := packs.Name
|
||||
|
||||
// Versions column
|
||||
versions := strings.Trim(strings.Replace(fmt.Sprint(packs.Versions), " ", ", ", -1), "[]")
|
||||
|
||||
// Render the columns.
|
||||
columns := []string{name, versions}
|
||||
rows = append(rows, cmdutil.TableRow{Columns: columns})
|
||||
}
|
||||
cmdutil.PrintTable(cmdutil.Table{
|
||||
Headers: headers,
|
||||
Rows: rows,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// policyPacksJSON is the shape of the --json output of this command. When --json is passed, we print an array
|
||||
// of policyPacksJSON objects. While we can add fields to this structure in the future, we should not change
|
||||
// existing fields.
|
||||
type policyPacksJSON struct {
|
||||
Name string `json:"name"`
|
||||
Versions []int `json:"versions"`
|
||||
}
|
||||
|
||||
func formatPolicyPacksJSON(policyPacks apitype.ListPolicyPacksResponse) error {
|
||||
output := make([]policyPacksJSON, len(policyPacks.PolicyPacks))
|
||||
for i, pack := range policyPacks.PolicyPacks {
|
||||
output[i] = policyPacksJSON{
|
||||
Name: pack.Name,
|
||||
Versions: pack.Versions,
|
||||
}
|
||||
}
|
||||
return printJSON(output)
|
||||
}
|
|
@ -134,3 +134,30 @@ type PolicyPackMetadata struct {
|
|||
DisplayName string `json:"displayName"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
// ListPolicyPacksResponse is the response to list an organization's
|
||||
// Policy Packs.
|
||||
type ListPolicyPacksResponse struct {
|
||||
PolicyPacks []PolicyPackWithVersions `json:"policyPacks"`
|
||||
}
|
||||
|
||||
// PolicyPackWithVersions details the specifics of a Policy Pack and all its available versions.
|
||||
type PolicyPackWithVersions struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Versions []int `json:"versions"`
|
||||
}
|
||||
|
||||
// ListPolicyGroupsResponse lists a summary of the organization's Policy Groups.
|
||||
type ListPolicyGroupsResponse struct {
|
||||
PolicyGroups []PolicyGroupSummary `json:"policyGroups"`
|
||||
}
|
||||
|
||||
// PolicyGroupSummary details the name, applicable stacks and the applied Policy
|
||||
// Packs for an organization's Policy Group.
|
||||
type PolicyGroupSummary struct {
|
||||
Name string `json:"name"`
|
||||
IsOrgDefault bool `json:"isOrgDefault"`
|
||||
NumStacks int `json:"numStacks"`
|
||||
NumEnabledPolicyPacks int `json:"numEnabledPolicyPacks"`
|
||||
}
|
||||
|
|
|
@ -120,6 +120,12 @@ type Backend interface {
|
|||
// GetPolicyPack returns a PolicyPack object tied to this backend, or nil if it cannot be found.
|
||||
GetPolicyPack(ctx context.Context, policyPack string, d diag.Sink) (PolicyPack, error)
|
||||
|
||||
// ListPolicyGroups returns all Policy Groups for an organization in this backend or an error if it cannot be found.
|
||||
ListPolicyGroups(ctx context.Context, orgName string) (apitype.ListPolicyGroupsResponse, error)
|
||||
|
||||
// ListPolicyPacks returns all Policy Packs for an organization in this backend, or an error if it cannot be found.
|
||||
ListPolicyPacks(ctx context.Context, orgName string) (apitype.ListPolicyPacksResponse, error)
|
||||
|
||||
// SupportsOrganizations tells whether a user can belong to multiple organizations in this backend.
|
||||
SupportsOrganizations() bool
|
||||
// ParseStackReference takes a string representation and parses it to a reference which may be used for other
|
||||
|
|
|
@ -226,6 +226,14 @@ func (b *localBackend) GetPolicyPack(ctx context.Context, policyPack string,
|
|||
return nil, fmt.Errorf("File state backend does not support resource policy")
|
||||
}
|
||||
|
||||
func (b *localBackend) ListPolicyGroups(ctx context.Context, orgName string) (apitype.ListPolicyGroupsResponse, error) {
|
||||
return apitype.ListPolicyGroupsResponse{}, fmt.Errorf("File state backend does not support resource policy")
|
||||
}
|
||||
|
||||
func (b *localBackend) ListPolicyPacks(ctx context.Context, orgName string) (apitype.ListPolicyPacksResponse, error) {
|
||||
return apitype.ListPolicyPacksResponse{}, fmt.Errorf("File state backend does not support resource policy")
|
||||
}
|
||||
|
||||
// SupportsOrganizations tells whether a user can belong to multiple organizations in this backend.
|
||||
func (b *localBackend) SupportsOrganizations() bool {
|
||||
return false
|
||||
|
|
|
@ -466,6 +466,14 @@ func (b *cloudBackend) GetPolicyPack(ctx context.Context, policyPack string,
|
|||
cl: client.NewClient(b.CloudURL(), apiToken, d)}, nil
|
||||
}
|
||||
|
||||
func (b *cloudBackend) ListPolicyGroups(ctx context.Context, orgName string) (apitype.ListPolicyGroupsResponse, error) {
|
||||
return b.client.ListPolicyGroups(ctx, orgName)
|
||||
}
|
||||
|
||||
func (b *cloudBackend) ListPolicyPacks(ctx context.Context, orgName string) (apitype.ListPolicyPacksResponse, error) {
|
||||
return b.client.ListPolicyPacks(ctx, orgName)
|
||||
}
|
||||
|
||||
// SupportsOrganizations tells whether a user can belong to multiple organizations in this backend.
|
||||
func (b *cloudBackend) SupportsOrganizations() bool {
|
||||
return true
|
||||
|
|
|
@ -97,8 +97,20 @@ func getStackPath(stack StackIdentifier, components ...string) string {
|
|||
return path.Join(append([]string{prefix}, components...)...)
|
||||
}
|
||||
|
||||
// publishPolicyPackPath returns the API path to for the given organization with the given
|
||||
// components joined with path separators and appended to the organization root.
|
||||
// listPolicyGroupsPath returns the path for an API call to the Pulumi service to list the Policy Groups
|
||||
// in a Pulumi organization.
|
||||
func listPolicyGroupsPath(orgName string) string {
|
||||
return fmt.Sprintf("/api/orgs/%s/policygroups", orgName)
|
||||
}
|
||||
|
||||
// listPolicyPacksPath returns the path for an API call to the Pulumi service to list the Policy Packs
|
||||
// in a Pulumi organization.
|
||||
func listPolicyPacksPath(orgName string) string {
|
||||
return fmt.Sprintf("/api/orgs/%s/policypacks", orgName)
|
||||
}
|
||||
|
||||
// publishPolicyPackPath returns the path for an API call to the Pulumi service to publish a new Policy Pack
|
||||
// in a Pulumi organization.
|
||||
func publishPolicyPackPath(orgName string) string {
|
||||
return fmt.Sprintf("/api/orgs/%s/policypacks", orgName)
|
||||
}
|
||||
|
@ -497,6 +509,26 @@ func (pc *Client) StartUpdate(ctx context.Context, update UpdateIdentifier,
|
|||
return resp.Version, resp.Token, nil
|
||||
}
|
||||
|
||||
// ListPolicyGroups lists all `PolicyGroups` the organization has in the Pulumi service.
|
||||
func (pc *Client) ListPolicyGroups(ctx context.Context, orgName string) (apitype.ListPolicyGroupsResponse, error) {
|
||||
var resp apitype.ListPolicyGroupsResponse
|
||||
err := pc.restCall(ctx, "GET", listPolicyGroupsPath(orgName), nil, nil, &resp)
|
||||
if err != nil {
|
||||
return resp, errors.Wrapf(err, "List Policy Groups failed")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListPolicyPacks lists all `PolicyPack` the organization has in the Pulumi service.
|
||||
func (pc *Client) ListPolicyPacks(ctx context.Context, orgName string) (apitype.ListPolicyPacksResponse, error) {
|
||||
var resp apitype.ListPolicyPacksResponse
|
||||
err := pc.restCall(ctx, "GET", listPolicyPacksPath(orgName), nil, nil, &resp)
|
||||
if err != nil {
|
||||
return resp, errors.Wrapf(err, "List Policy Packs failed")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// PublishPolicyPack publishes a `PolicyPack` to the Pulumi service. If it's successful, it returns
|
||||
// the version that was published.
|
||||
func (pc *Client) PublishPolicyPack(ctx context.Context, orgName string,
|
||||
|
|
|
@ -84,6 +84,14 @@ func (be *MockBackend) URL() string {
|
|||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (be *MockBackend) ListPolicyGroups(context.Context, string) (apitype.ListPolicyGroupsResponse, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (be *MockBackend) ListPolicyPacks(context.Context, string) (apitype.ListPolicyPacksResponse, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (be *MockBackend) GetPolicyPack(
|
||||
ctx context.Context, policyPack string, d diag.Sink) (PolicyPack, error) {
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package ints
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -36,8 +37,36 @@ func TestPolicy(t *testing.T) {
|
|||
os.Setenv("TEST_POLICY_PACK", policyPackName)
|
||||
e.RunCommand("pulumi", "policy", "publish", orgName)
|
||||
|
||||
// Check the policy ls commands.
|
||||
packsOutput, _ := e.RunCommand("pulumi", "policy", "ls", "--json")
|
||||
var packs []policyPacksJSON
|
||||
assertJSON(e, packsOutput, &packs)
|
||||
|
||||
groupsOutput, _ := e.RunCommand("pulumi", "policy", "group", "ls", "--json")
|
||||
var groups []policyGroupsJSON
|
||||
assertJSON(e, groupsOutput, &groups)
|
||||
|
||||
// Enable, Disable and then Delete the Policy Pack.
|
||||
e.RunCommand("pulumi", "policy", "enable", fmt.Sprintf("%s/%s", orgName, policyPackName), "1")
|
||||
e.RunCommand("pulumi", "policy", "disable", fmt.Sprintf("%s/%s", orgName, policyPackName), "1")
|
||||
e.RunCommand("pulumi", "policy", "rm", fmt.Sprintf("%s/%s", orgName, policyPackName), "1")
|
||||
}
|
||||
|
||||
type policyPacksJSON struct {
|
||||
Name string `json:"name"`
|
||||
Versions []int `json:"versions"`
|
||||
}
|
||||
|
||||
type policyGroupsJSON struct {
|
||||
Name string `json:"name"`
|
||||
Default bool `json:"default"`
|
||||
NumPolicyPacks int `json:"numPolicyPacks"`
|
||||
NumStacks int `json:"numStacks"`
|
||||
}
|
||||
|
||||
func assertJSON(e *ptesting.Environment, out string, respObj interface{}) {
|
||||
err := json.Unmarshal([]byte(out), &respObj)
|
||||
if err != nil {
|
||||
e.Errorf("unable to unmarshal %v", out)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue