Skip to content

Commit

Permalink
add object lock configuration APIs (#1153)
Browse files Browse the repository at this point in the history
  • Loading branch information
balamurugana authored and kannappanr committed Nov 10, 2019
1 parent b80ac70 commit 30047d6
Show file tree
Hide file tree
Showing 8 changed files with 735 additions and 19 deletions.
228 changes: 228 additions & 0 deletions api-object-lock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
* MinIO Go Library for Amazon S3 Compatible Cloud Storage
* Copyright 2019 MinIO, 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 minio

import (
"bytes"
"context"
"encoding/xml"
"fmt"
"net/http"
"net/url"
"time"

"github.com/minio/minio-go/v6/pkg/s3utils"
)

// RetentionMode - object retention mode.
type RetentionMode string

const (
// Governance - goverance mode.
Governance RetentionMode = "GOVERNANCE"

// Compliance - compliance mode.
Compliance RetentionMode = "COMPLIANCE"
)

func (r RetentionMode) String() string {
return string(r)
}

// IsValid - check whether this retention mode is valid or not.
func (r RetentionMode) IsValid() bool {
return r == Governance || r == Compliance
}

// ValidityUnit - retention validity unit.
type ValidityUnit string

const (
// Days - denotes no. of days.
Days ValidityUnit = "DAYS"

// Years - denotes no. of years.
Years ValidityUnit = "YEARS"
)

func (unit ValidityUnit) String() string {
return string(unit)
}

// IsValid - check whether this validity unit is valid or not.
func (unit ValidityUnit) isValid() bool {
return unit == Days || unit == Years
}

// Retention - bucket level retention configuration.
type Retention struct {
Mode RetentionMode
Validity time.Duration
}

func (r Retention) String() string {
return fmt.Sprintf("{Mode:%v, Validity:%v}", r.Mode, r.Validity)
}

// IsEmpty - returns whether retention is empty or not.
func (r Retention) IsEmpty() bool {
return r.Mode == "" || r.Validity == 0
}

// objectLockConfig - object lock configuration specified in
// https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_ObjectLockConfiguration.html
type objectLockConfig struct {
XMLNS string `xml:"xmlns,attr,omitempty"`
XMLName xml.Name `xml:"ObjectLockConfiguration"`
ObjectLockEnabled string `xml:"ObjectLockEnabled"`
Rule *struct {
DefaultRetention struct {
Mode RetentionMode `xml:"Mode"`
Days *uint `xml:"Days"`
Years *uint `xml:"Years"`
} `xml:"DefaultRetention"`
} `xml:"Rule,omitempty"`
}

func newObjectLockConfig(mode *RetentionMode, validity *uint, unit *ValidityUnit) (*objectLockConfig, error) {
config := &objectLockConfig{
ObjectLockEnabled: "Enabled",
}

if mode != nil && validity != nil && unit != nil {
if !mode.IsValid() {
return nil, fmt.Errorf("invalid retention mode `%v`", mode)
}

if !unit.isValid() {
return nil, fmt.Errorf("invalid validity unit `%v`", unit)
}

config.Rule = &struct {
DefaultRetention struct {
Mode RetentionMode `xml:"Mode"`
Days *uint `xml:"Days"`
Years *uint `xml:"Years"`
} `xml:"DefaultRetention"`
}{}

config.Rule.DefaultRetention.Mode = *mode
if *unit == Days {
config.Rule.DefaultRetention.Days = validity
} else {
config.Rule.DefaultRetention.Years = validity
}

return config, nil
}

if mode == nil && validity == nil && unit == nil {
return config, nil
}

return nil, fmt.Errorf("all of retention mode, validity and validity unit must be passed")
}

// SetBucketObjectLockConfig sets object lock configuration in given bucket. mode, validity and unit are either all set or all nil.
func (c Client) SetBucketObjectLockConfig(bucketName string, mode *RetentionMode, validity *uint, unit *ValidityUnit) error {
// Input validation.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return err
}

// Get resources properly escaped and lined up before
// using them in http request.
urlValues := make(url.Values)
urlValues.Set("object-lock", "")

config, err := newObjectLockConfig(mode, validity, unit)
if err != nil {
return err
}

configData, err := xml.Marshal(config)
if err != nil {
return err
}

reqMetadata := requestMetadata{
bucketName: bucketName,
queryValues: urlValues,
contentBody: bytes.NewReader(configData),
contentLength: int64(len(configData)),
contentMD5Base64: sumMD5Base64(configData),
contentSHA256Hex: sum256Hex(configData),
}

// Execute PUT bucket object lock configuration.
resp, err := c.executeMethod(context.Background(), "PUT", reqMetadata)
defer closeResponse(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return httpRespToErrorResponse(resp, bucketName, "")
}
}
return nil
}

// GetBucketObjectLockConfig gets object lock configuration of given bucket.
func (c Client) GetBucketObjectLockConfig(bucketName string) (mode *RetentionMode, validity *uint, unit *ValidityUnit, err error) {
// Input validation.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return nil, nil, nil, err
}

urlValues := make(url.Values)
urlValues.Set("object-lock", "")

// Execute GET on bucket to list objects.
resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{
bucketName: bucketName,
queryValues: urlValues,
contentSHA256Hex: emptySHA256Hex,
})
defer closeResponse(resp)
if err != nil {
return nil, nil, nil, err
}

config := &objectLockConfig{}
if err = xml.NewDecoder(resp.Body).Decode(config); err != nil {
return nil, nil, nil, err
}

if config.Rule != nil {
mode = &config.Rule.DefaultRetention.Mode
if config.Rule.DefaultRetention.Days != nil {
validity = config.Rule.DefaultRetention.Days
days := Days
unit = &days
} else {
validity = config.Rule.DefaultRetention.Years
years := Years
unit = &years
}

return mode, validity, unit, nil
}

return nil, nil, nil, nil
}
93 changes: 85 additions & 8 deletions api-put-bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,7 @@ import (

/// Bucket operations

// MakeBucket creates a new bucket with bucketName.
//
// Location is an optional argument, by default all buckets are
// created in US Standard Region.
//
// For Amazon S3 for more supported regions - http://docs.aws.amazon.com/general/latest/gr/rande.html
// For Google Cloud Storage for more supported regions - https://cloud.google.com/storage/docs/bucket-locations
func (c Client) MakeBucket(bucketName string, location string) (err error) {
func (c Client) makeBucket(bucketName string, location string, objectLockEnabled bool) (err error) {
defer func() {
// Save the location into cache on a successful makeBucket response.
if err == nil {
Expand Down Expand Up @@ -66,6 +59,12 @@ func (c Client) MakeBucket(bucketName string, location string) (err error) {
bucketLocation: location,
}

if objectLockEnabled {
headers := make(http.Header)
headers.Add("x-amz-bucket-object-lock-enabled", "true")
reqMetadata.customHeader = headers
}

// If location is not 'us-east-1' create bucket location config.
if location != "us-east-1" && location != "" {
createBucketConfig := createBucketConfiguration{}
Expand Down Expand Up @@ -98,6 +97,28 @@ func (c Client) MakeBucket(bucketName string, location string) (err error) {
return nil
}

// MakeBucket creates a new bucket with bucketName.
//
// Location is an optional argument, by default all buckets are
// created in US Standard Region.
//
// For Amazon S3 for more supported regions - http://docs.aws.amazon.com/general/latest/gr/rande.html
// For Google Cloud Storage for more supported regions - https://cloud.google.com/storage/docs/bucket-locations
func (c Client) MakeBucket(bucketName string, location string) (err error) {
return c.makeBucket(bucketName, location, false)
}

// MakeBucketWithObjectLock creates a object lock enabled new bucket with bucketName.
//
// Location is an optional argument, by default all buckets are
// created in US Standard Region.
//
// For Amazon S3 for more supported regions - http://docs.aws.amazon.com/general/latest/gr/rande.html
// For Google Cloud Storage for more supported regions - https://cloud.google.com/storage/docs/bucket-locations
func (c Client) MakeBucketWithObjectLock(bucketName string, location string) (err error) {
return c.makeBucket(bucketName, location, true)
}

// SetBucketPolicy set the access permissions on an existing bucket.
func (c Client) SetBucketPolicy(bucketName, policy string) error {
// Input validation.
Expand Down Expand Up @@ -304,3 +325,59 @@ func (c Client) SetBucketNotification(bucketName string, bucketNotification Buck
func (c Client) RemoveAllBucketNotification(bucketName string) error {
return c.SetBucketNotification(bucketName, BucketNotification{})
}

var (
versionEnableConfig = []byte("<VersioningConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Status>Enabled</Status></VersioningConfiguration>")
versionEnableConfigLen = int64(len(versionEnableConfig))
versionEnableConfigMD5Sum = sumMD5Base64(versionEnableConfig)
versionEnableConfigSHA256 = sum256Hex(versionEnableConfig)

versionDisableConfig = []byte("<VersioningConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Status>Suspended</Status></VersioningConfiguration>")
versionDisableConfigLen = int64(len(versionDisableConfig))
versionDisableConfigMD5Sum = sumMD5Base64(versionDisableConfig)
versionDisableConfigSHA256 = sum256Hex(versionDisableConfig)
)

func (c Client) setVersioning(bucketName string, config []byte, length int64, md5sum, sha256sum string) error {
// Input validation.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return err
}

// Get resources properly escaped and lined up before
// using them in http request.
urlValues := make(url.Values)
urlValues.Set("versioning", "")

reqMetadata := requestMetadata{
bucketName: bucketName,
queryValues: urlValues,
contentBody: bytes.NewReader(config),
contentLength: length,
contentMD5Base64: md5sum,
contentSHA256Hex: sha256sum,
}

// Execute PUT to set a bucket versioning.
resp, err := c.executeMethod(context.Background(), "PUT", reqMetadata)
defer closeResponse(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return httpRespToErrorResponse(resp, bucketName, "")
}
}
return nil
}

// EnableVersioning - Enable object versioning in given bucket.
func (c Client) EnableVersioning(bucketName string) error {
return c.setVersioning(bucketName, versionEnableConfig, versionEnableConfigLen, versionEnableConfigMD5Sum, versionEnableConfigSHA256)
}

// DisableVersioning - Disable object versioning in given bucket.
func (c Client) DisableVersioning(bucketName string) error {
return c.setVersioning(bucketName, versionDisableConfig, versionDisableConfigLen, versionDisableConfigMD5Sum, versionDisableConfigSHA256)
}
Loading

0 comments on commit 30047d6

Please sign in to comment.