mirror of
https://github.com/containers/podman.git
synced 2025-05-30 07:04:03 +08:00
Migrate podman inspect and tag to image library
Signed-off-by: baude <bbaude@redhat.com> Closes: #525 Approved by: baude
This commit is contained in:
@ -180,7 +180,7 @@ func createCmd(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data, err := libpod.GetImageData(newImage)
|
data, err := newImage.Inspect()
|
||||||
createConfig, err := parseCreateOpts(c, runtime, newImage.Names()[0], data)
|
createConfig, err := parseCreateOpts(c, runtime, newImage.Names()[0], data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -133,7 +133,7 @@ func iterateInput(c *cli.Context, args []string, runtime *libpod.Runtime, inspec
|
|||||||
inspectError = errors.Wrapf(err, "error getting image %q", input)
|
inspectError = errors.Wrapf(err, "error getting image %q", input)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
data, err = libpod.GetImageData(image)
|
data, err = image.Inspect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
inspectError = errors.Wrapf(err, "error parsing image data %q", image.ID())
|
inspectError = errors.Wrapf(err, "error parsing image data %q", image.ID())
|
||||||
break
|
break
|
||||||
@ -146,7 +146,7 @@ func iterateInput(c *cli.Context, args []string, runtime *libpod.Runtime, inspec
|
|||||||
inspectError = errors.Wrapf(err, "error getting image %q", input)
|
inspectError = errors.Wrapf(err, "error getting image %q", input)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
data, err = libpod.GetImageData(image)
|
data, err = image.Inspect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
inspectError = errors.Wrapf(err, "error parsing image data %q", image.ID())
|
inspectError = errors.Wrapf(err, "error parsing image data %q", image.ID())
|
||||||
break
|
break
|
||||||
|
@ -54,7 +54,7 @@ func runCmd(c *cli.Context) error {
|
|||||||
rtc := runtime.GetConfig()
|
rtc := runtime.GetConfig()
|
||||||
newImage, err := runtime.ImageRuntime().New(c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{})
|
newImage, err := runtime.ImageRuntime().New(c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{})
|
||||||
|
|
||||||
data, err := libpod.GetImageData(newImage)
|
data, err := newImage.Inspect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/containers/image/docker/reference"
|
|
||||||
"github.com/containers/storage"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/projectatomic/libpod/libpod"
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,51 +27,15 @@ func tagCmd(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
defer runtime.Shutdown(false)
|
defer runtime.Shutdown(false)
|
||||||
|
|
||||||
newImage := runtime.NewImage(args[0])
|
newImage, err := runtime.ImageRuntime().NewFromLocal(args[0])
|
||||||
newImage.GetLocalImageName()
|
|
||||||
|
|
||||||
img, err := runtime.GetImage(newImage.LocalName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if img == nil {
|
|
||||||
return errors.New("null image")
|
|
||||||
}
|
|
||||||
err = addImageNames(runtime, img, args[1:])
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error adding names %v to image %q", args[1:], args[0])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addImageNames(runtime *libpod.Runtime, image *storage.Image, addNames []string) error {
|
for _, tagName := range args[1:] {
|
||||||
// Add tags to the names if applicable
|
if err := newImage.TagImage(tagName); err != nil {
|
||||||
names, err := expandedTags(addNames)
|
return errors.Wrapf(err, "error adding '%s' to image %q", tagName, newImage.InputName)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, name := range names {
|
|
||||||
if err := runtime.TagImage(image, name); err != nil {
|
|
||||||
return errors.Wrapf(err, "error adding name (%v) to image %q", name, image.ID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func expandedTags(tags []string) ([]string, error) {
|
|
||||||
expandedNames := []string{}
|
|
||||||
for _, tag := range tags {
|
|
||||||
var labelName string
|
|
||||||
name, err := reference.Parse(tag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "error parsing tag %q", name)
|
|
||||||
}
|
|
||||||
if _, ok := name.(reference.NamedTagged); ok {
|
|
||||||
labelName = name.String()
|
|
||||||
} else {
|
|
||||||
labelName = name.String() + ":latest"
|
|
||||||
}
|
|
||||||
expandedNames = append(expandedNames, labelName)
|
|
||||||
}
|
|
||||||
return expandedNames, nil
|
|
||||||
}
|
|
||||||
|
@ -930,7 +930,7 @@ func (c *Container) addImageVolumes(g *generate.Generator) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
imageData, err := GetImageData(newImage)
|
imageData, err := newImage.Inspect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,21 @@ import (
|
|||||||
"github.com/projectatomic/libpod/pkg/util"
|
"github.com/projectatomic/libpod/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// imageConversions is used to cache image "cast" types
|
||||||
|
type imageConversions struct {
|
||||||
|
imgRef types.Image
|
||||||
|
storeRef types.ImageReference
|
||||||
|
}
|
||||||
|
|
||||||
// Image is the primary struct for dealing with images
|
// Image is the primary struct for dealing with images
|
||||||
// It is still very much a work in progress
|
// It is still very much a work in progress
|
||||||
type Image struct {
|
type Image struct {
|
||||||
// Adding these two structs for now but will cull when we near
|
// Adding these two structs for now but will cull when we near
|
||||||
// completion of this library.
|
// completion of this library.
|
||||||
|
imageConversions
|
||||||
inspect.ImageData
|
inspect.ImageData
|
||||||
inspect.ImageResult
|
inspect.ImageResult
|
||||||
|
inspectInfo *types.ImageInspectInfo
|
||||||
InputName string
|
InputName string
|
||||||
Local bool
|
Local bool
|
||||||
//runtime *libpod.Runtime
|
//runtime *libpod.Runtime
|
||||||
@ -226,11 +234,22 @@ func (i *Image) ID() string {
|
|||||||
return i.image.ID
|
return i.image.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Digest returns the image's Manifest
|
// Digest returns the image's digest
|
||||||
func (i *Image) Digest() digest.Digest {
|
func (i *Image) Digest() digest.Digest {
|
||||||
return i.image.Digest
|
return i.image.Digest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manifest returns the image's manifest as a byte array
|
||||||
|
// and manifest type as a string. The manifest type is
|
||||||
|
// MediaTypeImageManifest from ociv1.
|
||||||
|
func (i *Image) Manifest() ([]byte, string, error) {
|
||||||
|
imgRef, err := i.toImageRef()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return imgRef.Manifest()
|
||||||
|
}
|
||||||
|
|
||||||
// Names returns a string array of names associated with the image
|
// Names returns a string array of names associated with the image
|
||||||
func (i *Image) Names() []string {
|
func (i *Image) Names() []string {
|
||||||
return i.image.Names
|
return i.image.Names
|
||||||
@ -253,20 +272,6 @@ func (i *Image) Remove(force bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func annotations(manifest []byte, manifestType string) map[string]string {
|
|
||||||
annotations := make(map[string]string)
|
|
||||||
switch manifestType {
|
|
||||||
case ociv1.MediaTypeImageManifest:
|
|
||||||
var m ociv1.Manifest
|
|
||||||
if err := json.Unmarshal(manifest, &m); err == nil {
|
|
||||||
for k, v := range m.Annotations {
|
|
||||||
annotations[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompose an Image
|
// Decompose an Image
|
||||||
func (i *Image) Decompose() error {
|
func (i *Image) Decompose() error {
|
||||||
return types2.NotImplementedError
|
return types2.NotImplementedError
|
||||||
@ -349,6 +354,14 @@ func getImageDigest(src types.ImageReference, ctx *types.SystemContext) (string,
|
|||||||
|
|
||||||
// TagImage adds a tag to the given image
|
// TagImage adds a tag to the given image
|
||||||
func (i *Image) TagImage(tag string) error {
|
func (i *Image) TagImage(tag string) error {
|
||||||
|
decomposedTag, err := decompose(tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// If the input does not have a tag, we need to add one (latest)
|
||||||
|
if !decomposedTag.isTagged {
|
||||||
|
tag = fmt.Sprintf("%s:%s", tag, decomposedTag.tag)
|
||||||
|
}
|
||||||
tags := i.Names()
|
tags := i.Names()
|
||||||
if util.StringInSlice(tag, tags) {
|
if util.StringInSlice(tag, tags) {
|
||||||
return nil
|
return nil
|
||||||
@ -426,7 +439,14 @@ func (i *Image) MatchesID(id string) bool {
|
|||||||
|
|
||||||
// toStorageReference returns a *storageReference from an Image
|
// toStorageReference returns a *storageReference from an Image
|
||||||
func (i *Image) toStorageReference() (types.ImageReference, error) {
|
func (i *Image) toStorageReference() (types.ImageReference, error) {
|
||||||
return is.Transport.ParseStoreReference(i.imageruntime.store, i.ID())
|
if i.storeRef == nil {
|
||||||
|
storeRef, err := is.Transport.ParseStoreReference(i.imageruntime.store, i.ID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i.storeRef = storeRef
|
||||||
|
}
|
||||||
|
return i.storeRef, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToImageRef returns an image reference type from an image
|
// ToImageRef returns an image reference type from an image
|
||||||
@ -437,6 +457,7 @@ func (i *Image) ToImageRef() (types.Image, error) {
|
|||||||
|
|
||||||
// toImageRef returns an Image Reference type from an image
|
// toImageRef returns an Image Reference type from an image
|
||||||
func (i *Image) toImageRef() (types.Image, error) {
|
func (i *Image) toImageRef() (types.Image, error) {
|
||||||
|
if i.imgRef == nil {
|
||||||
ref, err := is.Transport.ParseStoreReference(i.imageruntime.store, "@"+i.ID())
|
ref, err := is.Transport.ParseStoreReference(i.imageruntime.store, "@"+i.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error parsing reference to image %q", i.ID())
|
return nil, errors.Wrapf(err, "error parsing reference to image %q", i.ID())
|
||||||
@ -445,7 +466,9 @@ func (i *Image) toImageRef() (types.Image, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error reading image %q", i.ID())
|
return nil, errors.Wrapf(err, "error reading image %q", i.ID())
|
||||||
}
|
}
|
||||||
return imgRef, nil
|
i.imgRef = imgRef
|
||||||
|
}
|
||||||
|
return i.imgRef, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// sizer knows its size.
|
// sizer knows its size.
|
||||||
@ -507,6 +530,44 @@ func (i *Image) Dangling() bool {
|
|||||||
|
|
||||||
// Labels returns the image's labels
|
// Labels returns the image's labels
|
||||||
func (i *Image) Labels() (map[string]string, error) {
|
func (i *Image) Labels() (map[string]string, error) {
|
||||||
|
imgInspect, err := i.imageInspectInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return imgInspect.Labels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotations returns the annotations of an image
|
||||||
|
func (i *Image) Annotations() (map[string]string, error) {
|
||||||
|
manifest, manifestType, err := i.Manifest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
annotations := make(map[string]string)
|
||||||
|
switch manifestType {
|
||||||
|
case ociv1.MediaTypeImageManifest:
|
||||||
|
var m ociv1.Manifest
|
||||||
|
if err := json.Unmarshal(manifest, &m); err == nil {
|
||||||
|
for k, v := range m.Annotations {
|
||||||
|
annotations[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return annotations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ociv1Image converts and image to an imgref and then an
|
||||||
|
// ociv1 image type
|
||||||
|
func (i *Image) ociv1Image() (*ociv1.Image, error) {
|
||||||
|
imgRef, err := i.toImageRef()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return imgRef.OCIConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||||
|
if i.inspectInfo == nil {
|
||||||
sr, err := i.toStorageReference()
|
sr, err := i.toStorageReference()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -519,7 +580,64 @@ func (i *Image) Labels() (map[string]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return imgInspect.Labels, nil
|
i.inspectInfo = imgInspect
|
||||||
|
}
|
||||||
|
return i.inspectInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect returns an image's inspect data
|
||||||
|
func (i *Image) Inspect() (*inspect.ImageData, error) {
|
||||||
|
ociv1Img, err := i.ociv1Image()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info, err := i.imageInspectInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
annotations, err := i.Annotations()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
size, err := i.Size()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoDigests []string
|
||||||
|
for _, name := range i.Names() {
|
||||||
|
repoDigests = append(repoDigests, strings.SplitN(name, ":", 2)[0]+"@"+i.Digest().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := i.DriverData()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &inspect.ImageData{
|
||||||
|
ID: i.ID(),
|
||||||
|
RepoTags: i.Names(),
|
||||||
|
RepoDigests: repoDigests,
|
||||||
|
Comment: ociv1Img.History[0].Comment,
|
||||||
|
Created: ociv1Img.Created,
|
||||||
|
Author: ociv1Img.Author,
|
||||||
|
Architecture: ociv1Img.Architecture,
|
||||||
|
Os: ociv1Img.OS,
|
||||||
|
ContainerConfig: &ociv1Img.Config,
|
||||||
|
Version: info.DockerVersion,
|
||||||
|
Size: int64(*size),
|
||||||
|
VirtualSize: int64(*size),
|
||||||
|
Annotations: annotations,
|
||||||
|
Digest: i.Digest(),
|
||||||
|
Labels: info.Labels,
|
||||||
|
RootFS: &inspect.RootFS{
|
||||||
|
Type: ociv1Img.RootFS.Type,
|
||||||
|
Layers: ociv1Img.RootFS.DiffIDs,
|
||||||
|
},
|
||||||
|
GraphDriver: driver,
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import imports and image into the store and returns an image
|
// Import imports and image into the store and returns an image
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
package libpod
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
digest "github.com/opencontainers/go-digest"
|
|
||||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/projectatomic/libpod/libpod/image"
|
|
||||||
"github.com/projectatomic/libpod/pkg/inspect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetImageData returns an image's inspect data
|
|
||||||
func GetImageData(img *image.Image) (*inspect.ImageData, error) {
|
|
||||||
imgRef, err := img.ToImageRef()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
size, err := imgRef.Size()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
manifest, manifestType, err := imgRef.Manifest()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "error reading manifest for image %q", img.ID)
|
|
||||||
}
|
|
||||||
imgDigest := digest.Digest("")
|
|
||||||
if len(manifest) > 0 {
|
|
||||||
imgDigest = digest.Canonical.FromBytes(manifest)
|
|
||||||
}
|
|
||||||
annotations := annotations(manifest, manifestType)
|
|
||||||
|
|
||||||
ociv1Img, err := imgRef.OCIConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "error getting oci image info %q", img.ID)
|
|
||||||
}
|
|
||||||
info, err := imgRef.Inspect()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "error getting image info %q", img.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
var repoDigests []string
|
|
||||||
for _, name := range img.Names() {
|
|
||||||
repoDigests = append(repoDigests, strings.SplitN(name, ":", 2)[0]+"@"+imgDigest.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := img.DriverData()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &inspect.ImageData{
|
|
||||||
ID: img.ID(),
|
|
||||||
RepoTags: img.Names(),
|
|
||||||
RepoDigests: repoDigests,
|
|
||||||
Comment: ociv1Img.History[0].Comment,
|
|
||||||
Created: ociv1Img.Created,
|
|
||||||
Author: ociv1Img.Author,
|
|
||||||
Architecture: ociv1Img.Architecture,
|
|
||||||
Os: ociv1Img.OS,
|
|
||||||
ContainerConfig: &ociv1Img.Config,
|
|
||||||
Version: info.DockerVersion,
|
|
||||||
Size: size,
|
|
||||||
VirtualSize: size,
|
|
||||||
Annotations: annotations,
|
|
||||||
Digest: imgDigest,
|
|
||||||
Labels: info.Labels,
|
|
||||||
RootFS: &inspect.RootFS{
|
|
||||||
Type: ociv1Img.RootFS.Type,
|
|
||||||
Layers: ociv1Img.RootFS.DiffIDs,
|
|
||||||
},
|
|
||||||
GraphDriver: driver,
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func annotations(manifest []byte, manifestType string) map[string]string {
|
|
||||||
annotations := make(map[string]string)
|
|
||||||
switch manifestType {
|
|
||||||
case ociv1.MediaTypeImageManifest:
|
|
||||||
var m ociv1.Manifest
|
|
||||||
if err := json.Unmarshal(manifest, &m); err == nil {
|
|
||||||
for k, v := range m.Annotations {
|
|
||||||
annotations[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return annotations
|
|
||||||
}
|
|
@ -961,7 +961,7 @@ func (r *Runtime) GetImages(params *ImageFilterParams, filters ...ImageFilter) (
|
|||||||
var imagesFiltered []*image.Image
|
var imagesFiltered []*image.Image
|
||||||
|
|
||||||
for _, img := range images {
|
for _, img := range images {
|
||||||
info, err := GetImageData(img)
|
info, err := img.Inspect()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1062,7 +1062,7 @@ func (r *Runtime) ParseImageFilter(imageInput, filter string) (*ImageFilterParam
|
|||||||
params.Label = pair[1]
|
params.Label = pair[1]
|
||||||
case "before":
|
case "before":
|
||||||
if img, err := findImageInSlice(images, pair[1]); err == nil {
|
if img, err := findImageInSlice(images, pair[1]); err == nil {
|
||||||
info, err := GetImageData(img)
|
info, err := img.Inspect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1072,7 +1072,7 @@ func (r *Runtime) ParseImageFilter(imageInput, filter string) (*ImageFilterParam
|
|||||||
}
|
}
|
||||||
case "since":
|
case "since":
|
||||||
if img, err := findImageInSlice(images, pair[1]); err == nil {
|
if img, err := findImageInSlice(images, pair[1]); err == nil {
|
||||||
info, err := GetImageData(img)
|
info, err := img.Inspect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user