Skip to content

Instantly share code, notes, and snippets.

@forkbombe
Last active April 14, 2020 08:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save forkbombe/b722fbe5da7e6da627b76ed0f22f6b2f to your computer and use it in GitHub Desktop.
Save forkbombe/b722fbe5da7e6da627b76ed0f22f6b2f to your computer and use it in GitHub Desktop.
PHP Class for Putting, Getting and Deleting objects from Linode Object Storage
<?php
/**
* A class for reading and writing to
* Linode Object Storage Buckets
*
* Usage
* -----
* $los = new \Linode\ObjectStorage(
* $apikey,
* $accesskey,
* $filesize_limit = false | integer (bytes)
* );
*
* PUT
* ---
* $response = $los->put(
* $object_path = '/tmp/something.jpg',
* $cluster = 'us-east-1',
* $bucket = 'testbucket'
* )
*
* stdClass Object
* (
* [success] => 1
* [key] => something.jpg
* )
*
* GET
* ---
* $response = $los->get(
* $object_key = 'something.jpg',
* $cluster = 'us-east-1',
* $bucket = 'testbucket'
* )
*
* stdClass Object
* (
* [url] => ###
* [key] => something.jpg
* )
*
* DELETE
* ------
* $response = $los->delete(
* $object_key = 'something.jpg',
* $cluster = 'us-east-1',
* $bucket = 'testbucket'
* )
*
* stdClass Object
* (
* [success] => 1
* [key] => something.jpg
* )
*/
namespace Linode;
class ObjectStorage {
/**
* API Endpoint for Linode Object Storage
* @var string
*/
private $endpoint =
"https://api.linode.com/v4beta/object-storage";
/**
* Linode API Key
* @var array|false|string
*/
private $api_key;
/**
* Access key for Object Storage
* @var array|false|string
*/
private $access_key;
/**
* File size limit (bytes)
* i.e. 50MB = 50000000
* @var bool / integer
*/
private $filesize_limit;
/**
* Default cURL options to pass
* @var array
*/
private $curl_default_opts =
array(
CURLOPT_PORT =>
443,
CURLOPT_FRESH_CONNECT =>
true,
CURLOPT_FOLLOWLOCATION =>
false,
CURLOPT_FAILONERROR =>
true,
CURLOPT_RETURNTRANSFER =>
true,
CURLOPT_TIMEOUT =>
200
);
/**
* Store error in variable
* for convenience
* @var bool
*/
public $errors;
/**
* LinodeObjectStore constructor.
* @param bool $api_key
* @param bool $access_key
*/
public function __construct(
$api_key = false,
$access_key = false,
$filesize_limit = false
) {
if(!$api_key) {
$this->api_key =
getenv('LINODE_API_KEY');
} else {
$this->api_key =
$api_key;
}
if(!$access_key) {
$this->access_key =
getenv('LINODE_BUCKET_ACCESS_KEY');
} else {
$this->access_key =
$access_key;
}
if(!$this->api_key) {
return
$this->error(
'api_key',
'API Key required'
);
}
if(!$this->access_key) {
return
$this->error(
'access_key',
'Access Key required'
);
}
$this->filesize_limit =
$filesize_limit;
return false;
}
/**
* Puts an object in bucket
* @param $object_path
* @param $cluster
* @param $bucket
* @return object|string
*/
public function put($object_path, $cluster, $bucket) {
if($this->filesize_limit) {
$filesize =
filesize($object_path);
if($filesize>$this->filesize_limit) {
return
$this->error(
'File',
'File `'.basename($object_path).'` too large'
);
}
}
$bucket_exists
= $this->bucket_exists(
$cluster,
$bucket
);
if(isset($bucket_exists->errors)) {
return $bucket_exists;
}
if($this->object_exists(
basename($object_path),
$cluster,
$bucket)
) {
return
$this->error(
'File',
'File with key name `'.basename($object_path).'` already exists'
);
}
$mime =
$this->get_mime($object_path);
if(isset($mime->errors)) {
return $mime;
}
$presigned =
$this->get_presigned_url(
basename($object_path),
'PUT',
$cluster,
$bucket,
$mime
);
if(isset($presigned->errors)) {
return $presigned;
}
$object =
fopen($object_path, 'r');
$headers =
array(
'Content-Type: '.$mime
);
$options =
$this->curl_default_opts;
$options[CURLOPT_URL] =
$presigned->url;
$options[CURLOPT_PUT] =
true;
$options[CURLOPT_INFILE] =
$object;
$options[CURLOPT_INFILESIZE] =
filesize($object_path);
$options[CURLOPT_HTTPHEADER] =
$headers;
$curl =
curl_init();
curl_setopt_array(
$curl, $options
);
$response =
curl_exec($curl);
fclose($object);
if($response===false) {
return
$this->error(
'bucket',
curl_error($curl)
);
}
curl_close($curl);
return (object) array(
'success' => true,
'key' => basename($object_path)
);
}
/**
* Gets an object in bucket
* @param $object_key
* @param $cluster
* @param $bucket
* @return object
*/
public function get(
$object_key,
$cluster,
$bucket
) {
$bucket_exists =
$this->bucket_exists(
$cluster,
$bucket
);
if(isset($bucket_exists->errors)) {
return $bucket_exists;
}
$presigned =
$this->get_presigned_url(
$object_key,
'GET',
$cluster,
$bucket
);
if(isset($presigned->errors)) {
return $presigned;
}
if($presigned->exists) {
$output = array(
'url' =>
$presigned->url,
'key' =>
$object_key
);
} else {
$output =
$this->error(
'File',
'File does not exist'
);
}
return (object) $output;
}
/**
* Delete an object in bucket
* @param $object_key
* @param $cluster
* @param $bucket
* @return object
*/
public function delete(
$object_key,
$cluster,
$bucket
) {
$bucket_exists =
$this->bucket_exists(
$cluster,
$bucket
);
if(isset($bucket_exists->errors)) {
return $bucket_exists;
}
$object =
$this->object_exists(
$object_key,
$cluster,
$bucket
);
if(!$object) {
return
$this->error(
'Object',
'Object does not exist'
);
}
$presigned =
$this->get_presigned_url(
$object_key,
'DELETE',
$cluster,
$bucket
);
if(isset($presigned->errors)) {
return $presigned;
}
$options =
$this->curl_default_opts;
$options[CURLOPT_URL] =
$presigned->url;
$options[CURLOPT_CUSTOMREQUEST] =
'DELETE';
$curl =
curl_init();
curl_setopt_array(
$curl, $options
);
$response =
curl_exec($curl);
if($response===false) {
return
$this->error(
'bucket',
curl_error($curl)
);
}
curl_close($curl);
return (object) array(
'success' => true,
'key' => $object_key
);
}
/**
* Get MIME type of local file
* @param $object
* @return object|string
*/
private function get_mime($object) {
if(!file_exists($object)) {
return
$this->error(
'get_mime',
'File does not exist'
);
}
return mime_content_type($object);
}
/**
* Check bucket name exists in cluster
* @param $cluster
* @param $bucket
* @return object
*/
private function bucket_exists(
$cluster,
$bucket
) {
$headers =
array(
'Authorization: Bearer ' . $this->api_key
);
$endpoint =
$this->endpoint . '/buckets/' . $cluster . '/' . $bucket;
$options =
$this->curl_default_opts;
$options[CURLOPT_URL] =
$endpoint;
$options[CURLOPT_HTTPHEADER] =
$headers;
$curl =
curl_init();
curl_setopt_array(
$curl, $options
);
$response =
curl_exec($curl);
if($response===false) {
return
$this->error(
'bucket',
curl_error($curl)
);
}
curl_close($curl);
return (object) json_decode($response);
}
/**
* Checks if object exists in bucket
* @param $object_key
* @param $cluster
* @param $bucket
* @return bool|object
*/
private function object_exists($object_key, $cluster, $bucket) {
$presigned =
$this->get_presigned_url(
$object_key,
'GET',
$cluster,
$bucket
);
if(isset($presigned->errors)) {
return $presigned;
}
$options =
$this->curl_default_opts;
$options[CURLOPT_URL] =
$presigned->url;
$curl =
curl_init();
curl_setopt_array(
$curl, $options
);
$response =
curl_exec($curl);
curl_close($curl);
if($response===false) {
return false;
}
return true;
}
/**
* Returns a presigned URL for putting objects
* @param $object_key
* @param $mime_type
* @param $method
* @param $cluster
* @param $bucket
* @return object
*/
public function get_presigned_url(
$object_key,
$method,
$cluster,
$bucket,
$mime_type = false
) {
$post_data = json_encode(
array(
"method" => $method,
"name" => $object_key,
"content_type" => ($mime_type ? $mime_type : '')
)
);
$headers =
array(
'Authorization: Bearer ' . $this->api_key,
'Content-Type: application/json'
);
$endpoint =
$this->endpoint . '/buckets/' . $cluster . '/' . $bucket . '/object-url';
$options =
$this->curl_default_opts;
$options[CURLOPT_URL] =
$endpoint;
$options[CURLOPT_HTTPHEADER] =
$headers;
$options[CURLOPT_POSTFIELDS] =
$post_data;
$curl = curl_init();
curl_setopt_array(
$curl, $options
);
$response =
curl_exec($curl);
if($response===false) {
return
$this->error(
'bucket',
curl_error($curl)
);
}
curl_close($curl);
return (object) json_decode($response);
}
/**
* Return error object
* @param $type
* @param $message
* @return object
*/
private function error($type, $message) {
$this->errors = (object) array(
'errors' =>
array(
$type => $message
)
);
return $this->errors;
}
}
@mauriblint
Copy link

Hi @bambattajb! Thanks for sharing this class. I'm trying to put a file to a Linode Object Storage bucket. I created a bucket on Linode, and a pair of access keys. I got [bucket] => The requested URL returned error: 401 UNAUTHORIZED

Do you need to configure anything else? I'm trying also with S3 PHP Class but not luck.

Thanks!

@forkbombe
Copy link
Author

Hi @bambattajb! Thanks for sharing this class. I'm trying to put a file to a Linode Object Storage bucket. I created a bucket on Linode, and a pair of access keys. I got [bucket] => The requested URL returned error: 401 UNAUTHORIZED

Do you need to configure anything else? I'm trying also with S3 PHP Class but not luck.

Thanks!

Not sure if the endpoint has changed since I did this.
Look at line 63 where the API endoint has been explicitly defined. I don't know if this is correct anymore.
https://www.linode.com/docs/platform/object-storage/how-to-use-object-storage/#s3cmd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment