Skip to content

Commit 37e7bcf

Browse files
Steve BruntonSteve Brunton
authored andcommitted
initial github release commit
0 parents  commit 37e7bcf

File tree

408 files changed

+229054
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

408 files changed

+229054
-0
lines changed

LICENSE

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2019, sbrunton at gmail dot com
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are met:
8+
9+
* Redistributions of source code must retain the above copyright notice, this
10+
list of conditions and the following disclaimer.
11+
12+
* Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
* Neither the name of the copyright holder nor the names of its
17+
contributors may be used to endorse or promote products derived from
18+
this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+

README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
### OCI Metadata Script Handler
2+
3+
#### Overview
4+
5+
The intent of this is to make it easy to be able to run a startup or shutdown script on an instance for purposes such
6+
as configuration, ping back announcements or anything else one may want to do during these lifecycle steps.
7+
8+
Scripts are defined in the instance metadata as being either of `startup` or `shutdown` and can be sourced from
9+
either local storage, metadata value, remote url or
10+
[Object Storage](https://docs.cloud.oracle.com/iaas/Content/Object/Concepts/objectstorageoverview.htm). If more
11+
than one source is supplied both will be executed with remote being run first.
12+
13+
`startup-script` Examples :
14+
15+
```
16+
"startup-script" : "/opt/service/start/bootstrap.py"
17+
```
18+
19+
```
20+
"startup-script" : "IyEvdXNyL2Jpbi9lbnYgYmFzaAoKZWNobyAibXkgYXdlc29tZSBzdGFydHVwIHNjcmlwdCIK"
21+
```
22+
23+
The first will execute the script that is contained locally on the host instance. It will be copied into a
24+
temporary location before execution.
25+
26+
The second is the actual script data base64 encoded and supplied in the instance metadata. It will be base64 decoded
27+
into a temporary location under a temporary filename and then executed.
28+
29+
`startup-script-url` Examples:
30+
31+
```
32+
"startup-script-url" : "oci://bucket@namespace/bootstrap.py"
33+
```
34+
35+
```
36+
"startup-script-url" : "https://trusted-remote-server.io/boot/bootstrap.py"
37+
```
38+
39+
The first will fetch the script from an Object Storage bucket in the namespace, store in a temporary working
40+
directory and then execute it. It is the responsibility of the owner to make sure that the instances have access to
41+
the bucket.
42+
43+
The second will make a request to the specified location, download to a temporary directory and execute the
44+
payload. Be warned that this should be a trusted known site and that egress to the remote location is allowed.
45+
46+
#### Object Storage Example
47+
48+
If Object storage will be the source for the scripts a
49+
[policy](https://docs.cloud.oracle.com/iaas/Content/Identity/Concepts/policygetstarted.htm)
50+
will need to be applied such that the instances
51+
can easily have read-only access to the buckets in the namespace to retrieve the scripts from. A definition for
52+
[Dynamic Groups](https://docs.cloud.oracle.com/iaas/Content/Identity/Tasks/managingdynamicgroups.htm) is also helpful
53+
for being able to allow instances in this group to have the policy for read-only access to the buckets. Using
54+
[Terraform](https://www.terraform.io/) it could be something like :
55+
56+
57+
```
58+
resource "oci_identity_dynamic_group" "ro_script_buckets" {
59+
compartment_id = "${var.tenancy_ocid}"
60+
name = "ro-script-buckets"
61+
description = "dynamic group for reading startup/shutdown script buckets"
62+
matching_rule = <<EOF
63+
Any {instance.compartment.id = 'ocid1.compartment.oc1..aaaaaxutgua',
64+
instance.compartment.id = 'ocid1.compartment.oc1..aaaaallwh6mv7avuozfsus4yaia',
65+
instance.compartment.id = 'ocid1.compartment.oc1..aaaaaaaaszz2v4gyfza'}
66+
EOF
67+
}
68+
```
69+
70+
Where the `instance.compartment.id` values are those of compartments where instances will be created in and will
71+
require access to the startup or shutdown scripts buckets.
72+
73+
```
74+
resource "oci_identity_policy" "script_buckets_read" {
75+
depends_on = ["oci_identity_dynamic_group.ro_script_buckets"]
76+
compartment_id = "${var.tenancy_ocid}"
77+
name = "script-buckets-read"
78+
description = "read only policy for scripts buckets"
79+
statements = [
80+
"allow dynamic-group ro-script-buckets to read buckets in compartment Bucket_Compartment where any {target.bucket.name='startup-scripts', target.bucket.name='shutdown-scripts'}",
81+
"allow dynamic-group ro-script-buckets to read objects in compartment Bucket_Compartment where any {target.bucket.name='startup-scripts', target.bucket.name='shutdown-scripts'}"
82+
]
83+
}
84+
```
85+
86+
This will allow the instances in the dynamic group created above to access the buckets with the names `startup-scripts`
87+
and `shutdown-scripts`.

VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.0.2

executor.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os/exec"
7+
8+
log "github.com/sirupsen/logrus"
9+
)
10+
11+
const defaultShell = "/bin/bash"
12+
13+
func (sm *ScriptManager) RunScript(sn string) error {
14+
15+
s := fmt.Sprintf("%s/%s", sm.WorkDir, sn)
16+
17+
log.Debugf("about to run : %s", s)
18+
19+
cmd := exec.Command(defaultShell, s)
20+
21+
cmdReader, err := cmd.StdoutPipe()
22+
if err != nil {
23+
return fmt.Errorf("error creating stdout pipe for '%s' : %s", s, err)
24+
}
25+
26+
scanner := bufio.NewScanner(cmdReader)
27+
28+
go func() {
29+
for scanner.Scan() {
30+
log.Infof("%s | %s", sn, scanner.Text())
31+
}
32+
}()
33+
34+
err = cmd.Start()
35+
if err != nil {
36+
return fmt.Errorf("error starting command '%s' : %s", s, err)
37+
}
38+
39+
err = cmd.Wait()
40+
if err != nil {
41+
return fmt.Errorf("error waiting for command '%s': %s", s, err)
42+
}
43+
44+
return nil
45+
}

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module oci-metadata-scripts
2+
3+
go 1.12
4+
5+
require (
6+
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
7+
github.com/oracle/oci-go-sdk v5.4.0+incompatible
8+
github.com/sirupsen/logrus v1.4.1
9+
github.com/stretchr/objx v0.2.0 // indirect
10+
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a // indirect
11+
)

go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
4+
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
5+
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
6+
github.com/oracle/oci-go-sdk v5.4.0+incompatible h1:fM4QtmPTmmrzcyvBQP1/deOodn1/zJSszrQmHotAb3Q=
7+
github.com/oracle/oci-go-sdk v5.4.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
8+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9+
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
10+
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
11+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
12+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13+
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
14+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
15+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
16+
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
17+
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
18+
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a h1:XCr/YX7O0uxRkLq2k1ApNQMims9eCioF9UpzIPBDmuo=
19+
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

instancemeta/instance_metadata.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2017 Oracle and/or its affiliates. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package instancemeta
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"net/http"
21+
)
22+
23+
const (
24+
baseURL = "http://169.254.169.254"
25+
metadataEndpoint = "/opc/v1/instance/"
26+
)
27+
28+
// InstanceMetadata holds the subset of the instance metadata retrieved from the
29+
// local OCI instance metadata API endpoint.
30+
// https://docs.us-phoenix-1.oraclecloud.com/Content/Compute/Tasks/gettingmetadata.htm
31+
type InstanceMetadata struct {
32+
CompartmentOCID string `json:"compartmentId"`
33+
Region string `json:"region"`
34+
Metadata map[string]string `json:"metadata"`
35+
}
36+
37+
// Interface defines how consumers access OCI instance metadata.
38+
type Interface interface {
39+
Get() (*InstanceMetadata, error)
40+
}
41+
42+
type metadataGetter struct {
43+
baseURL string
44+
client *http.Client
45+
}
46+
47+
// New returns the instance metadata for the host on which the code is being
48+
// executed.
49+
func New() Interface {
50+
return &metadataGetter{client: http.DefaultClient, baseURL: baseURL}
51+
}
52+
53+
// Get either returns the cached metadata for the current instance or queries
54+
// the instance metadata API, populates the cache, and returns the result.
55+
func (m *metadataGetter) Get() (*InstanceMetadata, error) {
56+
req, err := http.NewRequest("GET", m.baseURL+metadataEndpoint, nil)
57+
if err != nil {
58+
return nil, err
59+
}
60+
resp, err := m.client.Do(req)
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to get instance metadata: %v", err)
63+
64+
}
65+
defer resp.Body.Close()
66+
67+
if resp.StatusCode != http.StatusOK {
68+
return nil, fmt.Errorf("metadata endpoint returned status %d; expected 200 OK", resp.StatusCode)
69+
}
70+
71+
md := &InstanceMetadata{}
72+
err = json.NewDecoder(resp.Body).Decode(md)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
return md, nil
78+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2017 Oracle and/or its affiliates. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package instancemeta
16+
17+
type mockMetadataGetter struct {
18+
metadata *InstanceMetadata
19+
}
20+
21+
// NewMock returns a new mock OCI instance metadata getter.
22+
func NewMock(metadata *InstanceMetadata) Interface {
23+
return &mockMetadataGetter{metadata: metadata}
24+
}
25+
26+
func (m *mockMetadataGetter) Get() (*InstanceMetadata, error) {
27+
return m.metadata, nil
28+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2017 Oracle and/or its affiliates. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package instancemeta
16+
17+
import (
18+
"fmt"
19+
"net/http"
20+
"net/http/httptest"
21+
"reflect"
22+
"testing"
23+
)
24+
25+
const exampleResponse = `{
26+
"availabilityDomain" : "NWuj:PHX-AD-1",
27+
"compartmentId" : "ocid1.compartment.oc1..aaaaaaaa3um2atybwhder4qttfhgon4j3hcxgmsvnyvx4flfjyewkkwfzwnq",
28+
"displayName" : "trjl-kb8s-master",
29+
"id" : "ocid1.instance.oc1.phx.abyhqljtj775udgtbu7nddt6j2hqgxdsgrnpweepogvvsmqfppefewile5zq",
30+
"image" : "ocid1.image.oc1.phx.aaaaaaaaamx6ta37uxltor6n5lxfgd5lkb3lwmoqurlpn2x4dz5ockekiuea",
31+
"metadata" : {
32+
"ssh_authorized_keys" : "ssh-rsa some-key-data tlangfor@tlangfor-mac\n",
33+
"startup-script" : "/usr/local/bin/baas-bootstrap.py",
34+
"startup-script-url" : "oci://bucket@namespace/baas-bootstrap.py"
35+
},
36+
"region" : "phx",
37+
"shape" : "VM.Standard1.1",
38+
"state" : "Provisioning",
39+
"timeCreated" : 1496415602152
40+
}`
41+
42+
func TestGetMetadata(t *testing.T) {
43+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
44+
fmt.Fprintln(w, exampleResponse)
45+
}))
46+
defer ts.Close()
47+
getter := metadataGetter{client: ts.Client(), baseURL: ts.URL}
48+
meta, err := getter.Get()
49+
if err != nil {
50+
t.Fatalf("Uexpected error calling Get(): %v", err)
51+
}
52+
53+
expected := &InstanceMetadata{
54+
CompartmentOCID: "ocid1.compartment.oc1..aaaaaaaa3um2atybwhder4qttfhgon4j3hcxgmsvnyvx4flfjyewkkwfzwnq",
55+
Region: "phx",
56+
Metadata: map[string]string{
57+
"ssh_authorized_keys": "ssh-rsa some-key-data tlangfor@tlangfor-mac\n",
58+
"startup-script": "/usr/local/bin/baas-bootstrap.py",
59+
"startup-script-url": "oci://bucket@namespace/baas-bootstrap.py",
60+
},
61+
}
62+
if !reflect.DeepEqual(meta, expected) {
63+
t.Errorf("Get() => %+v, want %+v", meta, expected)
64+
}
65+
}

0 commit comments

Comments
 (0)